セキュリティパラノイアのためのArchLinux
あなたのPCは本当に安全ですか?
カフェでPC作業をしていて、ふと席を立った時。
あなたが寝ていて、PCの電源をつけっぱなしにしている時。
どこかの諜報機関にPCを奪われた時。
そんな時、あなたのプライバシーを守り切ることはできますか?
何をするか
壮大でカッコつけた書き出しにしましたが、今回やることは単純です。
- ArchLinux をインストールする
- OSの入ったパーティションをすべて暗号化する(/bootも!)
- 自分の鍵でセキュアブートを有効にして設定ファイルもGPGで署名
- BIOSをアクセスされないように色々する
ArchWiki に必要なことはすべて書いてありますが、情報が散らばっていたり日本語訳されていない部分もあるので備忘録がてら纏めました(ArchWiki の日本語訳できるほどの英語力はなくて...)。
なお、この記事ではインストール方法をすべて解説するわけではなく、重要なところをかいつまんで説明します。ArchLinux のインストール方法自体は別の記事やらWikiやらを参照してください。また、その影響でこの記事は中級者以上向けとなるかもしれません。
ArchLinux をインストールする
ライブディスクを用意する
https://www.archlinux.jp/download/ からイメージをダウンロードし、必ず署名を確認してください。
それを適当なメディアに焼いて適当に起動します。
パーティションを用意する
ディスクの用意
デュアルブートをしていないのであれば、このタイミングで SSD のデータをすべて完全削除するのが良いでしょう。以下のコマンドを実行します。
dd if=/dev/zero of=path/to/device status=progress
本来であれば if は /dev/urandom の方がデータ復旧が更に困難になり安全なのですが、urandom はマシンに存在するエントロピーを利用するため、(unblockとはいえ) 以下の様に速度が遅くなります。
なので、時間が余っているかディスクの容量が小さいのであれば if には /dev/urandom を指定すると良いでしょう。
sh-5.1$ time dd if=/dev/zero of=./test bs=1024 count=1000000
1000000+0 records in
1000000+0 records out
1024000000 bytes (1.0 GB, 977 MiB) copied, 5.21865 s, 196 MB/s
real 0m5.521s
user 0m1.085s
sys 0m4.414s
sh-5.1$ time dd if=/dev/urandom of=./test bs=1024 count=1000000
1000000+0 records in
1000000+0 records out
1024000000 bytes (1.0 GB, 977 MiB) copied, 13.8624 s, 73.9 MB/s
real 0m14.164s
user 0m1.336s
sys 0m12.805s
パーティションの分割とセットアップ
次に、3つのパーティションを用意します。
- EFI System パーティション
- 最低 260 MiB
- grub を復号化するバイナリが格納される
- /boot パーティション
- 512 MiB ぐらい
- LUKS1 で暗号化され、内部には vfat のファイルシステムが存在し、grub が格納される。
- main パーティション
- 残りすべて
- LUKS2 で暗号化され、LVM で / とスワップ領域に分割されている
fdisk 等でパーティションを切り分けます。最低サイズなどは守った方がいいですが、復号化にかかる時間が伸びている可能性がある(未検証)ので、/boot パーティションは小さめでも良いかもしれません。
以下、切り分けたデバイスをそれぞれ /dev/part1 /dev/part2 /dev/part3 とします。ここは各自 nmve0 とか sda とかに読み替えてください。
-
- 次のコマンドを実行してEFIファイルシステムを作ります。
# EFI パーティションを作る
mkfs.vfat /dev/part1
-
- cryptsetup ツールを使って /boot を作ります。
日本語Wiki には乗っていない情報ですが、GRUB は LUKS2 をサポートしていないっぽいので、LUKS1 を使います(実際に、LUKS2ではブートに失敗します)
# 暗号化パーティションを作る
cryptsetup luksFormat --type luks1 /dev/part2 # 起動時に毎回効かれるパスワードを入力
# 暗号化されたパーティションを開き、以降 /dev/mapper/cryptboot への読み書きを /dev/part2 へ変換してくれる様にする
cryptsetup open /dev/part2 cryptboot
# 実際にファイルシステムを作る
mkfs.vfat /dev/mapper/cryptboot
2021/9/1 追記
以上の手順で /boot のパーティションを作る場合、起動時に10秒から20秒ほどかかります。(詳しい原因はわかっていませんが、どうやらGRUBが非効率的な実装をしてるとかなんとか...)
一応の解決策として、パスワードの強度を下げることで対応することが可能です。もちろんその場合セキュリティ上のリスクは上がります。
それでも良い方は、次のコマンドで暗号ディスクを作ってください。
cryptsetup luksFormat --type luks1 /dev/part2 --iter-time=100
iter-time はデフォルトで 2000 になっているため、大幅に下げています。
こうすればパスワードのチェック時間が1秒ほどに短縮されます。
-
- 同様に / と SWAP を作る
part3 には実際には入力しないパスワードを設定します。そのため、ランダムなデータを keyfile として保存し、自動的に復号化されるようにします。
日本語ArchWikiのキーファイルの項目に詳しくまとまっています。おすすめは以下のコマンドで生成される keyfile です。
keyfile は /boot に保存される必要があるので、2 で作った cryptboot をマウントし、必ずその中で作業をしてください。それ以外の場所で作業をした場合は、作成した keyfile を /boot へ移した後、必ず shred コマンドを用いて完全削除を行ってください。
# /boot をマウント
mkdir tmp_boot && mount /dev/mapper/cryptboot ./tmp_boot && cd ./tmp_boot
# キーファイルを生成
dd bs=512 count=4 if=/dev/random of=./keyfile.img
次に、この鍵を用いて part3 を暗号化します。
# 暗号化パーティション作成
cryptsetup luksFormat /dev/part3 ./tmp_boot/keyfile.img
# /dev/mapper/cryptroot 作成
cryptsetup open /dev/part3 cryptroot
# LVM のための用意をし、/ と SWAP で分ける。
pvcreate /dev/mapper/cryptroot
vgcreate mygroup /dev/mapper/cryptroot
lvcreate -L 8G mygroup -n swap
lvcreate -l 100%FREE mygroup -n root
# ファイルシステムを作る
mkswap /dev/mygroup/swap
mkfs.btrfs /dev/mygroup/root
今回はメインで使うファイルシステムを btrfs にしました。お好きなものに変えていただいて構いません。
OSとブートローダ
さて、あとはいつも通り ArchLinux を入れましょう〜。
mount /dev/mygroup/root /mnt
mkdir /mnt/boot/
mount /dev/mapper/cryptboot /mnt/boot
mkdir /mnt/boot/efi
mount /dev/part1 /mnt/boot/efi
swapon /dev/mygroup/swap
pacstrap /mnt base linux linux-firmware
genfstab -U /mnt >> /mnt/etc/fstab # ちゃんとすべての設定が生成されているか確認すると良いかも!
# あとは chroot して locale とか timezone とか諸々。grub のパッケージも必要なので入れましょう。
GRUBの設定
まず、GRUB に何をしてほしいのか纏めましょう。
- GRUB のEFIバイナリを暗号化されていない /dev/part1 つまり /boot/efi に設置する。
- 起動時にパスワードプロンプトを表示し /dev/part2 を復号化できるようにする。
- 復号化した /boot にある keyfile.img で /dev/part3 を復号化し、/ にマウントしてカーネルを起動する。
です。順にやっていきましょう。
-
- GRUB のインストール
セキュアブートを考慮して、特殊なオプションを2つ渡します。"--modules=tpm --disable-shim-lock"
- GRUB のインストール
# GRUB を入れる
grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=grub --modules=tpm --disable-shim-lock --recheck
これで /boot/efi の下に grub に起動イメージが生成され、EFIアプリケーションにそのパスが追加されます(efibootmgr -v
で確認できます)。
-
- パスワードプロンプトとかを表示するようにする
GRUB は元となる設定ファイル /etc/default/grub と /etc/grub.d/* から /boot/grub/grub.cfg に置く設定ファイルを生成します。
暗号化されたパーティションから OS を起動するにあたり、/etc/default/grub を編集します。
まず GRUB の暗号起動モードを有効にします。
GRUB_ENABLE_CRYPTODISK=y
また、/etc/mkinitcpio.conf も編集します。
FILES に /boot/keyfile.img を追加し、initramfs に keyfile.img が含まれるようにします。
次に HOOKS で、encrypt や resume などを追加します。keyboard が先にこないと駄目だったりと面倒なのでとりあえず私の設定を貼ります。
HOOKS=(base udev autodetect modconf keyboard keymap encrypt lvm2 block resume filesystems fsck)
-
- / のマウント
復号とマウントのため、カーネルパラメータを設定します。
Linux カーネルには、起動時にカーネルパラメータを渡すことができ、起動後は /proc/cmdline で確認することができます。
boot パーティションや root パーティションを暗号化する場合、それらのパラメータを変更する必要があります。
カーネルパラメータは GRUB_CMDLINE_LINUX_DEFAULT で編集できます。
設定できるパラメータは Linux の公式ドキュメントにまとまっていますが、バカみてぇに多いです。
ので、ArchWikiが暗号関係のパラメータを纏めてくれています。ありがとう。https://wiki.archlinux.jp/index.php/Dm-crypt/%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0%E8%A8%AD%E5%AE%9A#encrypt_.E3.83.95.E3.83.83.E3.82.AF.E3.82.92.E4.BD.BF.E3.81.86
指定に使う UUID は lsblk --fs
で確認できます。
cryptkey=rootfs:/boot/keyfile.img cryptdevice=UUID=(/dev/part3のUUID):cryptroot root=/dev/mygroup/root resume=UUID=(/dev/mygroup/swapのUUID)
とすればおそらく問題ないでしょう。
以上の設定が終わったら initramfs を再生成し grub の設定を書き出て再起動します。
mkinitcpio -P
grub-mkconfig --output=/boot/grub/grub.cfg
成功すれば、起動時にパスワードを聞かれて復号化され、ちょっと待つと GRUB の選択画面が出てくるはずです。
そのまま ArchLinux を選択すれば、もう一度 /boot のパスワードを聞かれ、その後起動できると思います。
カーネルの起動の改良
無事に起動できたはいいのですが、/boot のパスワードを二回聞かれるのはなぜなのでしょうか。
それは、GRUB は入力されたパスワードをカーネルへ渡す方法を持っていないからです。
この問題を解決する方法は、/ の中に /boot パーティションの boot_keyfile を格納し、二回目はそれを用いて復号化する事です。二回目ではすでに / が使えるので、それが実現できます。
更に、LUKS には鍵を追加する仕組みがあるため、一回目のパスワードを平文保存する必要もありません。
どこに boot_keyfile を設置しても良いですが、ユーザーからは読めないようにすることで、万が一侵入されてもリスクを低減できます。例によって途中で作成したファイルなどは shred で消し飛ばしましょう。
今回は /root にします。
# /dev/part2 の2つ目の鍵の生成
dd bs=512 count=4 if=/dev/random of=/root/boot_keyfile.img
# その追加
cryptsetup luskAddKey /dev/part2 /root/boot_keyfile.img
また、このファイルも initramfs に乗っている必要があるので /etc/mkinitcpio.conf の FILES に追加しもう一度 mkinitcpio -P
を実行します。
最後にカーネルに対してそのファイルを使うように指示するため /etc/crypttab を作成し以下の内容を追記します。
cryptboot UUID=(/dev/part2のUUID) /root/bootkey.img luks
crypttab は fstab より先に読み込まれるため、ここで /dev/part2 を復号化することで fstab のマウントも成功します。
Secure Boot する
さて、ディスクを暗号化したため PC が盗まれたとしてもプライバシーは守れる様になりました。
しかし、悪意あるメイド攻撃をされた場合、情報が漏洩したりパスワードが漏れたりする可能性があります。
この問題の本質は、攻撃者の持ち込んだイメージが起動できてしまうことです。それを防ぐための仕組みが Secure Boot となります。
しかし Secure Boot をしていても防げない攻撃があります。それは、
- ブートローダーに署名できる組織が悪意を持っている場合
- Secure Boot を無効化できる場合
です。それぞれ対処していきます。
自分の鍵で署名
1 を対処していきます。
Secure Boot を有効にするにあたり、我々が取れる方法は2つあります。
- 署名済みブートローダーを使う
- 自分で署名する。
出荷される PC には通常 Windows が Microsoft の署名付きでインストールされています。その署名を検証できる様に事前に彼らの持つ公開鍵を持っているのです。
PreLoader や shim は Windows と同じ様に Microsoft によって署名されたブートローダーなので、Secure Boot を有効にしてもそのまま起動できます。
しかしこれは本当に安全でしょうか?Microsoft が鍵の管理を誤り外部に漏らしてしまったら?悪意を持つ攻撃者が Microsoft 自身だったら?
怖いですね。なので今回は 自分で署名する 方法を取ります。そうすることで自分で管理を間違えなければいいだけになります。(却って不安になる気もするけど仕方ない...)
例によって ArchWiki#セキュアブートに沿って進めていきます。
Wiki にきれいにまとまっているのでそのまま載せます。作業は /root などで行いましょう。
uuidgen --random > GUID.txt
openssl req -newkey rsa:2048 -nodes -keyout PK.key -new -x509 -sha256 -days 3650 -subj "/CN=my Platform Key/" -out PK.crt
openssl x509 -outform DER -in PK.crt -out PK.cer
cert-to-efi-sig-list -g "$(< GUID.txt)" PK.crt PK.esl
sign-efi-sig-list -g "$(< GUID.txt)" -k PK.key -c PK.crt PK PK.esl PK.auth
sign-efi-sig-list -g "$(< GUID.txt)" -c PK.crt -k PK.key PK /dev/null rm_PK.auth
openssl req -newkey rsa:2048 -nodes -keyout KEK.key -new -x509 -sha256 -days 3650 -subj "/CN=my Key Exchange Key/" -out KEK.crt
openssl x509 -outform DER -in KEK.crt -out KEK.cer
cert-to-efi-sig-list -g "$(< GUID.txt)" KEK.crt KEK.esl
sign-efi-sig-list -g "$(< GUID.txt)" -k PK.key -c PK.crt KEK KEK.esl KEK.auth
openssl req -newkey rsa:2048 -nodes -keyout db.key -new -x509 -sha256 -days 3650 -subj "/CN=my Signature Database key/" -out db.crt
openssl x509 -outform DER -in db.crt -out db.cer
cert-to-efi-sig-list -g "$(< GUID.txt)" db.crt db.esl
sign-efi-sig-list -g "$(< GUID.txt)" -k KEK.key -c KEK.crt db db.esl db.auth
鍵の生成が終わったら、その鍵を登録する必要があります。この作業はコマンドでもできるらしいですが、私の環境では失敗したので KeyTool を使います。
efitools パッケージをインストールすると /usr/share/efitools/efi/KeyTool.efi
にキーの設定ができるイメージが生成されます。後で使います。
さて、鍵の生成ができたら実際に署名します。
# GRUB の署名
sbsign --key db.key --cert db.crt --output path/to/grub.efi path/to/grub.efi
# KeyTool の署名
sbsign --key db.key --cert db.crt --output /boot/efi/KeyTool-signed.efi /usr/share/efitools/efi/KeyTool.efi
# (デュアルブートなら) Windows の署名
sbsign --key db.key --cert db.crt --output hoge/foo/Microsoft/Boot/bootmgr.efi hoge/foo/Microsoft/Boot/bootmgr.efi
# 鍵を一時的に EFI パーティションに置く
cp /root/secure_boot/* /boot/efi/secure_boot/
grub.efi の場所は efibootmgr -v
で確かめることができます。
さて、最後は KeyTool.efi を efi アプリケーションとして登録します。
efibootmgr -c -d /dev/part -p 1 -L key_app -l '\KeyTool-signed.efi'
/dev/part
は part1,2,3 を纏めたファイルで、-p 1
で EFI が格納されているパーティションを指定します。
最後に efibootmgr
で登録されているか確認をしたら再起動します。
BIOS の設定から Secure Boot の設定を User mode に移行し、key_app を起動します。
それぞれ既存の鍵を消し、作成した鍵を登録します。dbx はブラックリストなのでいじらなくて大丈夫です。
そうしたら BIOS の設定を開き Secure Boot を有効化します。その状態で GRUB を問題なく起動できたら成功です。
最後に、
# 攻撃者に key_app を開かせないため。delete でもいい
efibootmgr --bootnum (key_app の番号) --inactive
# /boot/efi においた鍵を消す
shred -n 3 /boot/efi/secure_boot/*
rm /boot/efi/secure_boot/*
堅牢なBIOS
さて、残す問題はあと一つです。
- Secure Boot を無効化できる場合
コレができたら何でもありです。そこで BIOS の設定にパスワードをかけます。メーカーごとに色々ありますので、適当に調べて設定してください。
私は起動する毎に聞かれるのは辛いので、設定や起動メニューにのみパスワードをかけてもいます。
しかしコレにも抜け道があります。分解され CMOS クリアをされた場合、パスワード認証を回避され、一連のトラストチェインが崩壊してしまいます。
そこで、開封防止シールなどを PC の裏のネジ部分に貼ることでその可能性を低くすることができます。
カーネルやinitramfs、grub.cfg を署名(2021/9/3 追記)
正直、そこまで意味があるのかわからないので飛ばしてもOKです。
現時点で署名できているのは GRUB の EFI アプリケーションのみです。トラストチェイン的には問題ありませんが、多重防御的に vmlinuz-linux や initramfs 、 grub.cfg もちゃんと署名してほしいですよね。
ということでやります。
Secureboot では openssl を使いましたが、ここでは GPG を使ってみます。作業は root に成って行ってください。
gpg --full-gen-key
鍵の種類は RSA and RSA を選びます。GRUB が対応していないのか別の原因があるのかわかりませんが、他の方式では全て失敗しました。
鍵の長さは任意です。
署名を補助してくれるツールとして grub2-signing-extension を使います。小さなシェルスクリプトで構成されているので不安な方は確認できます。
信用する鍵を /boot に格納します。
gpg --export > /boot/gpg.key
信用する鍵と、署名確認の強制などを設定ファイルに書き込みます。
/etc/grub.d/ の何処かに以下を追記します
insmod gcry_sha256
insmod gcry_rsa
trust /gpg.key
set check_signatures=enforce
設定を反映し、署名します。
grub-mkconfig --output=/boot/grub/grub.cfg
chmod a+rwx `tty`
grub-sign
コレで /boot 以下がすべて署名され、起動時に自動的に検証されます。
注意
linux のアップデート時など、ファイルが更新され新しく署名し直す時があると思います。
その時は grub-update-kernel-signature
を実行してください。
また、pacman.d/hooks の中に自動的に署名されるようにするのは難しいです。pacman が hook を実行する際は tty が割り当てられず、gpg がパスワードプロンプトを表示するための pinentry-curses がうまく動作しないためです。
tty shell を spawn したり gpg-agent で pinentry-program の変更を試したりしましたがうまくいきませんでした。
手っ取り早い解決方法は秘密鍵を平文保存することです。そうすれば自動で署名できるようになります。
まとめ
あなたはカフェで作業をしていて、少しの間席を外した。
PC の前に戻ってきたら
- PC の裏のシールが剥がれているか確認し、
- BIOS のパスワードを入力し、
- GRUB の復号パスを入力すれば、
あとは署名された安全なOSが手に入る。というわけです。コレでとりあえずの安心は手に入りましたね。
以上で、ちょっとは安全な ArchLinux の構成が終わりました。
しかし油断してはいけません。コレでも一部妥協はありますし、起動してから色々すれば様々なセキュリティホールが生まれるでしょう。
firewall や SELinux を使って運用面でもパラノイアになっていきましょう!
おつかれさまでした!
質問や誤字脱字、情報の間違いなどがあればコメントか私のついったーにお願いします〜