🔐

Manjaro LinuxをLUKSディスク暗号化+セキュアブート構成でインストールする

2024/10/14に公開

はじめに

大学の研究に使っているLinuxラップトップについて、共同研究契約の関係でPCのディスク暗号化が要求されるようになったためセットアップを行いました。これはその備忘録です。

なお、実際のセットアップの際には以下の記事を参考にさせていただきました。

今回設定した要件

攻撃者がラップトップに物理アクセス可能な状況でも簡単にはデータを読み出せないようにすること、です。そのために以下が必要です:

  • Linuxシステムの入っているパーティションはフルディスク暗号化されている
    • ただし利便性のためにTPM2デバイスによって暗号化ディスクは自動復号され、パスフレーズを入力することなくログインマネージャまで到達可能
  • ブートに必要なファイル群(*.efi, vmlinuz-linux)は署名されており、セキュアブートが有効

最終的に出来上がった構成

  • ブート用パーティション(/dev/nvme0n1p1 => /boot)
    • ファイルシステムはFAT32
    • 暗号化されていない
    • ブートに必要なファイル群は署名されており、UEFI内に保存された公開鍵(証明書)で検証可能
  • システム用パーティション(/dev/nvme0n1p2 => /dev/mapper/cryptolvm)
    • LUKSで暗号化されている
    • この上にLVMを使って作成された論理ボリュームが2つある
      • root
        • ファイルシステムはbtrfs
        • Manjaro Linuxがインストールされている
      • swap
        • swap領域

使用したマシン

Lenovo IdeaPad Pro 5i Gen 9 (14型):カスタマイズモデル

- 型式
CPU Core™ Ultra 5 125H
RAM LPDDR5X 32GB
GPU Intel® Arc™ graphics
SSD WD Black SN850X 1TB

作業手順

ManjaroのUSBブートドライブを作成し、セットアップしたいPC上でこれを起動した上で特権ユーザに昇格してから作業を始めていきます。

事前準備

  1. 以前に何らかのデータを(暗号化せずに)保存したことがあるディスクを利用する場合は、dm-crypt/ドライブの準備に従って当該ディスクをランダムデータで完全に上書きする。SSDの場合はメモリセルの消去を行ったほうが良い。
  2. 有線LANあるいはiwd等でインターネットに接続する。
  3. timedatectl set-ntp true でシステムクロックを合わせる。

ディスクのパーティショニングとフォーマット

今回はsystemd-bootを利用する都合上bootパーティションのファイルシステムはFAT32になっている必要があるため、以下のようにパーティションを切ります。私はGPartedを使ったため、ESPについては同時にフォーマットも行っています。

容量 パーティションタイプ ファイルシステム flag 説明
1GB Primary FAT32 boot,esp EFI System Partition
残り全て EFI System unformatted/unknown(まだフォーマットしない) - LVMを入れるパーティション

システム用パーティションの暗号化とLVMの構成

システム用パーティションをLUKSで暗号化し、その中にLVMでroot用とswap用の論理ボリュームを作っていきます。swap領域は慣例に従いRAMの2倍としました。

LUKSによるパーティションの暗号化
# パーティションをLUKSで暗号化
cryptsetup luksFormat /dev/nvme0n1p2  # ここで設定するパスフレーズは絶対に忘れないこと
# 上のパーティションを復号してマウント (/dev/mapper/cryptolvmから利用可能になる)
cryptsetup open --type luks /dev/nvme0n1p2 cryptolvm
LVMによる論理ボリュームの作成
# 物理ボリュームを作成
pvcreate /dev/mapper/cryptolvm
# systemという名前のボリュームグループを作成
vgcreate system /dev/mapper/cryptolvm
# ボリュームグループに論理ボリュームを作成
lvcreate -L 64G -n swap system # swap領域を確保
lvcreate -l 100%FREE -n root system # 残り全てをroot領域にする

LVM上のパーティションのフォーマット

フォーマット対象のボリュームがLVM上にあることに注意するだけです。

# root領域: btrfs
mkfs.btrfs /dev/mapper/system-root
# swap領域
mkswap /dev/mapper/system-swap

Btrfsサブボリュームの作成とマウント

将来的にpacmanの実行時にSnapperでスナップショットを撮るようにしたいので、このタイミングでサブボリュームを作ってから各サブボリュームをマウントしていきます。また、ルート(/)のスナップショットに /home, /var/log, /var/cache が含まれないように、これらはルート用サブボリュームの中に入れ子になった別のサブボリュームにします。(参考: 推奨ファイルシステムレイアウト

Btrfsサブボリュームの作成
## ルート用サブボリュームを作成してマウント
# 一時的にrootを直接マウント
mount /dev/mapper/system-root /mnt
# ルートにサブボリューム@を作る
btrfs subvolume create /mnt/@
# 一時マウントを解除
umount /mnt
# rootをサブボリュームとして再びマウント
mount -o compress-force=zstd,subvol=@ /dev/mapper/system-root /mnt

## home用サブボリュームを作成してマウント
btrfs subvolume create /mnt/@home
mkdir /mnt/home
mount -o compress-force=zstd,subvol=@home /dev/mapper/system-root /mnt/home

## log用サブボリュームを作成してマウント
btrfs subvolume create /mnt/@log
mkdir -p /mnt/var/log
mount -o compress-force=zstd,subvol=@log /dev/mapper/system-root /mnt/var/log

## cache用サブボリュームを作成してマウント
btrfs subvolume create /mnt/@cache
mkdir -p /mnt/var/cache
mount -o compress-force=zstd,subvol=@cache /dev/mapper/system-root /mnt/var/cache

マウントオプションの compress-force=zstd は必須ではありませんが、ディスクを効率的に利用できるらしいのでつけてみました。

/bootのマウント

bootは暗号化していないのでいつも通りマウントできます。fmaskとdmaskはファイルとディレクトリに対するパーミッションです。

mount --mkdir -o fmask=0137,dmask=0027 /dev/nvme0n1p1 /mnt/boot

Manjaro Linuxのインストール

さて、後はGUIを使って/dev/mapper/system-rootにManjaro Linuxを入れるだけ...と思っていたのですが、なんとManjaroのインストールメディアに付属しているGraphical Installerはlvm2モジュールをサポートしておらずLVM上にインストールできないようです。そこでガイドにしたがってマニュアルインストールを行わなければなりません。とはいえ、ここまでの手順が正しく実行できていれば6. Base installationと, 7. Base Configurationを実行するだけです。

/boot周りの構成について

上述のガイドはブートローダにGRUBを使うことを前提としているため/bootと/boot/efiがあるように書かれていますが、今回はsystemd-bootを使うためFAT32でフォーマットされた/bootだけでOKです。ガイドに従って作り直してしまわないように注意してください。

まず 6. Base installationを行います。$LINUXlinux611 などお好みのカーネルバージョンで置き換えてください。また、一般的なパッケージ群に加えてlvm2とcryptsetupが必要です。

basestrap /mnt base $LINUX dhcpcd networkmanager mkinitcpio efibootmgr vi vim sudo lvm2 cryptsetup intel-ucode

AMD製のCPUをお使いの方はamd-ucodeに置き換えてください。

次に 7. Base Configurationを行います。ここはガイドの通りに進めればOKですが、ガイド中でdhcpcdとNetworkManagerから1つ選ぶところではNetworkManagerをenableするのがおすすめです。

https://forum.manjaro.org/t/root-tip-how-to-do-a-manual-manjaro-installation/12507#h-7-base-configuration-19

/etc/fstabの編集

マウントオプションは適当です。

/etc/fstab
/dev/mapper/system-root  /           btrfs  defaults,autodefrag,noatime,lazytime,subvol=@,compress-force=zstd      0  0 
/dev/mapper/system-root  /home       btrfs  defaults,autodefrag,noatime,lazytime,subvol=@home,compress-force=zstd  0  0 
/dev/mapper/system-root  /var/log    btrfs  defaults,autodefrag,noatime,lazytime,subvol=@log,compress-force=zstd   0  0 
/dev/mapper/system-root  /var/cache  btrfs  defaults,autodefrag,noatime,lazytime,subvol=@cache,compress-force=zstd 0  0 
UUID=<UUID of ESP>       /boot       vfat   rw,relatime,fmask=0137,dmask=0027,utf8,errors=remount-ro               0  0
/dev/mapper/system-swap  none        swap   defaults                                                               0  0

/etc/mkinitcpio.confの編集とinitramfsの生成

ブート時にシステム用パーティションを復号しLVMを正常に認識するためには、initramfsがsd-encryptやlvm2[1]を含んでいる必要があります。また、busyboxベースではなくsystemdベースで起動するように設定していきます。

各HOOKの意味についてはArch Wikiを参照してください。
https://wiki.archlinux.jp/index.php/Mkinitcpio#HOOKS

/etc/mkinitcpio.conf
MODULES=(tpm_tis)
HOOKS=(base systemd keyboard autodetect modconf block sd-encrypt lvm2 filesystems)
HOOKS内にfsckを指定していない理由

Btrfsはfsckとは異なる独自のチェック機構を持っており、fsckは意味がないためです。詳しくは
cat $(which fsck.btrfs) で確認してみてください。

mkinitcpioを実行し、正常にイメージが生成されることを確認します。

mkinitcpio -P

systemd-bootのインストール

以下を実行するだけで/bootに勝手にインストールしてくれます。

bootctl install

次に、Manjaro Linux用のエントリを以下のように作成します。Linuxカーネルや各種イメージのパスは全て/bootからの相対パスです。

/boot/loader/entries/manjaro.conf
title  Manjaro Linux
linux  /vmlinuz-6.11-x86_64
initrd /intel-ucode.img
initrd /initramfs-6.11-x86_64.img
options luks.name=<UUID of encrypted partition>=cryptolvm root=/dev/mapper/system-root rootflags=subvol=@ resume=/dev/mapper/system-swap rw

<UUID of encrypted partition>は暗号化したパーティション(ここでは /dev/nvme0n1p2)のUUIDを調べて置き換えてください。ls -l /dev/disk/by-uuid で調べるのが便利です。
エントリを書き終わったらbootctl listで内容が正しいことを確認しておきましょう。

また、systemdをアップデートしたときにsystemd-bootもアップデートされるようにしておきます。

systemctl enable systemd-boot-update.service

再起動&セキュアブートSetup Modeに入る

再起動時にUEFIメニューに入り、セキュアブートのStatusをSetup Modeにして起動してください。ブートメッセージが途中で止まりパスフレーズが要求されるはずですので、cryptsetupしたときのパスフレーズを入力してください。パーティションが復号されManjaro Linuxが起動したらとりあえずここまでは成功です🎉

一般ユーザの追加

homectlを使うと便利です。

# systemd-homedを有効化して起動
systemctl enable --now systemd-homed.service
# ユーザーを作成
homectl create <username> --storage=luks --member-of==users,wheel

次に、visudoして以下の行をアンコメントし、sudoを使用可能にします。

## uncomment to allow members of group wheel to execute any command
%wheel ALL=(ALL:ALL) ALL

sudoが正しく使えることを確認したら、rootアカウントはロックしてしまいましょう。

passwd -l root

セキュアブートのセットアップ

まずsbctlとefitoolsをインストールしてください。

sudo pacman -S sbctl efitools

現在のセキュアブート関連のEFI変数をバックアップします。

for var in PK KEK db dbx
do
efi-readvar -v $var -o old_${var}.esl
done

セキュアブートがSetup Modeになっているか確認します。以下のように表示されればOKです。

正しい例
sbctl status
Installed:        ✘ Sbctl is not installed
Setup Mode:       ✘ Enabled
Secure Boot:      ✘ Disabled

ブート関連ファイル群の署名に使うための鍵ペアを作成し、UEFIに検証用公開鍵(証明書)を登録します。(正確にはセキュアブートでは数種類の鍵が使われます。また、sbctlのmanpageによると秘密鍵は/var/lib/sbctl/keysに保存されるようです)

sbctl create-keys
sbctl enroll-keys -m -f=db,KEK,PK

最後に、作成した鍵で署名を行います。まず署名が必要なファイルを調べます。

sbctl verify
Verifying file database and EFI images in /boot...
✘ /boot/vmlinuz-6.11-x86_64 is not signed
✘ /boot/EFI/BOOT/BOOTX64.EFI is not signed
✘ /boot/EFI/systemd/systemd-bootx64.efi is not signed

これらに対して順に署名しましょう。

sbctl sign -s /boot/EFI/BOOT/BOOTX64.EFI
✔ Signed /boot/EFI/BOOT/BOOTX64.EFI...
sbctl sign -s /boot/vmlinuz-6.11-x86_64
✔ Signed /boot/vmlinuz-6.11-x86_64...
sbctl sign -s /boot/EFI/systemd/systemd-bootx64.efi
✔ Signed /boot/EFI/systemd/systemd-bootx64.efi...

今回はsystemd-boot-updateを使用していますが、ブートローダのアップデート時には実際にブートローダがアップデートされるタイミングとsbctlに付属する自動署名用pacmanフックの実行タイミングの差異に伴う問題が起こることが知られています。これを回避するために、Arch Wikiが推奨する通り/usr/libにある元ファイルにも直接署名してしまいましょう。

sbctl sign -s -o /usr/lib/systemd/boot/efi/systemd-bootx64.efi.signed /usr/lib/systemd/boot/efi/systemd-bootx64.efi

ここまで終わったら再起動し、UEFIからセキュアブートを有効にして起動します。前回同様パスフレーズの入力を求められ、問題なくManjaro Linuxが立ち上がるはずです。

TPMにLUKSヘッダ内キースロットのロック解除キーを登録する

システム用パーティションを復号するためのマスターキーはLUKSヘッダのキースロットと呼ばれる場所に暗号化されて保存されており、そこからマスターキーを取り出すためにはパスフレーズや「キースロット解錠用のキー」が使われます。ここではこの「キースロット解錠用のキー」をTPMに登録することでシステム用パーティションを安全に自動復号可能にします。

シークレットをTPMに保存する
systemd-cryptenroll /dev/nvme0n1p2 --wipe-slot=tpm2 --tpm2-device=auto --tpm2-pcrs=7+12
tpm2-pcrs(Platform Configuration Registers)について

TPMはシステムの状態を測定することでPCRという値を計算しており、上で登録した「キースロット解錠用のキー」はそれが保存されたときのPCRとセットになっています。TPMが「キースロット解錠用のキー」を開放するよう要求されると、TPMはその時点のPCRとTPM内に保存されているPCRを比較し、一致する場合にのみ「キースロット解錠用のキー」を開放します。ここでどのような測定項目に基づいてPCRを計算するかを--tpm2-pcrsパラメタで指定することができ、systemd-cryptenrollのmanpage内の表Arch WikiのTPMのページで一覧を確認できます。例えばPCR 7では「セキュアブート関連のEFI変数の内容とブートローダの検証用証明書」が測定項目となるため、何者かがセキュアブートをオフにした場合はPCRの値が変化しTPMは開放を拒否する、といった挙動をします。

再起動し、パスフレーズなしで起動することを確認してください。お疲れ様でした🎉

残りのセットアップ

まあ、ここからがセットアップ本番ですよね。まずufwを入れて、次にお好みのデスクトップ環境を入れて、それからかっこいいプロンプトを入れたり、なんかいい感じのシステムモニタを入れたりしてみてください。(安全で)良きLinuxライフを!

番外編(このPCのセキュリティ強度について)

ディスク暗号化&セキュアブートの設定が完了したわけですが、この状態で実際にどんな攻撃が防げるのかいくつかのシナリオを考えてみたいと思います。

脅威モデルを以下のように設定します。

  • 攻撃者はこのPCに物理アクセス可能で、ディスクを取り外したりできる
  • 攻撃者はLinuxマシンを持っており、ディスクのパーティションをマウントして読み書きできる

攻撃者がこのPCのディスクを攻撃者のLinuxマシンに取り付けた場合

この場合は攻撃者が「キースロット解錠用のキー」を得る方法が存在せずLUKSパーティションを復号できないため、システム用パーティションのデータが読み取られることはありません。

攻撃者がこのPCにUSBディスクを取り付けて別のLinuxを起動しようとした場合

この場合はセキュアブートによってブートが拒否されます。攻撃者はUEFIメニューからセキュアブートをオフにすることで当該USBディスクからLinuxを起動することができますが、この場合はPCR7の値が変化しているためTPMは「キースロット解錠用のキー」の開放を拒み、TPM内のキーを使ってLUKSパーティションを復号することはできません。

攻撃者がこのPCのディスクを攻撃者のLinuxマシンに取り付けてブート用パーティション内のブートローダやカーネルを改ざんし、このPCに再び取り付けた上で起動しようとした場合

この場合はセキュアブートによって署名の不一致が発見されるためブートが拒否されます。攻撃者が私の署名用秘密鍵を用いて改ざんしたブートローダやカーネルに署名したくても、署名用秘密鍵は暗号化されたLUKSパーティションの中に保存されているため手に入りません。さらに、攻撃者が独自の鍵を作成して証明書をUEFIに登録した上で改ざん済みファイルを署名することでセキュアブートをオンにしたまま起動しようとしても、新しい鍵が登録されたことでPCR7の値が変化しているためTPMは「キースロット解錠用のキー」の開放を拒み、LUKSパーティションを復号することはできません。

攻撃者がこのPCのディスクを攻撃者のLinuxマシンに取り付けてブート用パーティション内のエントリを書き換えカーネルパラメータを変更し、このPCに再び取り付けた上で起動しようとした場合

この場合はPCR12の値が変化しているためTPMは「キースロット解錠用のキー」の開放を拒み、TPM内のキーを使ってLUKSパーティションを復号することはできません。

参考文献

https://wiki.archlinux.jp/index.php/Dm-crypt/システム全体の暗号化

https://wiki.archlinux.jp/index.php/Trusted_Platform_Module#LUKS_.E3.81.AB.E3.82.88.E3.82.8B.E4.BF.9D.E5.AD.98.E3.83.87.E3.83.BC.E3.82.BF.E3.81.AE.E6.9A.97.E5.8F.B7.E5.8C.96

脚注
  1. 以前はsd-lvm2と書いても良かったようなのですが今はlvm2と書くようです ↩︎

Discussion