セキュリティパラノイアに送る ArchLinux

# セキュリティパラノイアのためのArchLinux

あなたのPCは本当に安全ですか？

カフェでPC作業をしていて、ふと席を立った時。
あなたが寝ていて、PCの電源をつけっぱなしにしている時。
どこかの諜報機関にPCを奪われた時。

そんな時、あなたのプライバシーを守り切ることはできますか？

# 何をするか
壮大でカッコつけた書き出しにしましたが、今回やることは単純です。

- ArchLinux をインストールする
- OSの入ったパーティションをすべて暗号化する(/bootも！)
- 自分の鍵でセキュアブートを有効にして設定ファイルもGPGで署名
- BIOSをアクセスされないように色々する

ArchWiki に必要なことはすべて書いてありますが、情報が散らばっていたり日本語訳されていない部分もあるので備忘録がてら纏めました（ArchWiki の日本語訳できるほどの英語力はなくて...）。
なお、この記事ではインストール方法をすべて解説するわけではなく、重要なところをかいつまんで説明します。ArchLinux のインストール方法自体は別の記事やらWikiやらを参照してください。また、その影響でこの記事は中級者以上向けとなるかもしれません。

# ArchLinux をインストールする

## ライブディスクを用意する
[https://www.archlinux.jp/download/](https://www.archlinux.jp/download/) からイメージをダウンロードし、必ず署名を確認してください。

それを適当なメディアに焼いて適当に起動します。

## パーティションを用意する
### ディスクの用意
デュアルブートをしていないのであれば、このタイミングで SSD のデータをすべて完全削除するのが良いでしょう。以下のコマンドを実行します。

```sh
dd if=/dev/zero of=path/to/device status=progress
```

本来であれば if は /dev/urandom の方がデータ復旧が更に困難になり安全なのですが、urandom はマシンに存在するエントロピーを利用するため、(unblockとはいえ) 以下の様に速度が遅くなります。
なので、時間が余っているかディスクの容量が小さいのであれば if には /dev/urandom を指定すると良いでしょう。

```sh
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つのパーティションを用意します。

1. EFI System パーティション
    - 最低 260 MiB
    - grub を復号化するバイナリが格納される
2. /boot パーティション
    - 512 MiB ぐらい
    - LUKS1 で暗号化され、内部には vfat のファイルシステムが存在し、grub が格納される。
3. main パーティション
    - 残りすべて
    - LUKS2 で暗号化され、LVM で / とスワップ領域に分割されている

fdisk 等でパーティションを切り分けます。最低サイズなどは守った方がいいですが、復号化にかかる時間が伸びている可能性がある(未検証)ので、/boot パーティションは小さめでも良いかもしれません。
以下、切り分けたデバイスをそれぞれ /dev/part1 /dev/part2 /dev/part3 とします。ここは各自 nmve0 とか sda とかに読み替えてください。

- 1. 次のコマンドを実行してEFIファイルシステムを作ります。

```sh
# EFI パーティションを作る
mkfs.vfat /dev/part1
```

- 2. cryptsetup ツールを使って /boot を作ります。

日本語Wiki には乗っていない情報ですが、GRUB は LUKS2 をサポートしていないっぽいので、LUKS1 を使います（実際に、LUKS2ではブートに失敗します）

```sh
# 暗号化パーティションを作る
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が非効率的な実装をしてるとかなんとか...)
一応の解決策として、パスワードの強度を下げることで対応することが可能です。もちろんその場合セキュリティ上のリスクは上がります。
それでも良い方は、次のコマンドで暗号ディスクを作ってください。

```sh
cryptsetup luksFormat --type luks1 /dev/part2 --iter-time=100
```

iter-time はデフォルトで 2000 になっているため、大幅に下げています。
こうすればパスワードのチェック時間が1秒ほどに短縮されます。

- 3. 同様に / と SWAP を作る

part3 には実際には入力しないパスワードを設定します。そのため、ランダムなデータを keyfile として保存し、自動的に復号化されるようにします。
日本語ArchWikiの[キーファイル](https://wiki.archlinux.jp/index.php/Dm-crypt/%E3%83%87%E3%83%90%E3%82%A4%E3%82%B9%E3%81%AE%E6%9A%97%E5%8F%B7%E5%8C%96#.E3.82.AD.E3.83.BC.E3.83.95.E3.82.A1.E3.82.A4.E3.83.AB)の項目に詳しくまとまっています。おすすめは以下のコマンドで生成される keyfile です。
keyfile は /boot に保存される必要があるので、2 で作った cryptboot をマウントし、**必ず**その中で作業をしてください。それ以外の場所で作業をした場合は、作成した keyfile を /boot へ移した後、**必ず** shred コマンドを用いて完全削除を行ってください。

```sh
# /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 を暗号化します。

```sh
# 暗号化パーティション作成
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 を入れましょう〜。

```sh
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 に何をしてほしいのか纏めましょう。

1. GRUB のEFIバイナリを暗号化されていない /dev/part1 つまり /boot/efi に設置する。
2. 起動時にパスワードプロンプトを表示し /dev/part2 を復号化できるようにする。
3. 復号化した /boot にある keyfile.img で /dev/part3 を復号化し、/ にマウントしてカーネルを起動する。

です。順にやっていきましょう。

- 1. GRUB のインストール
セキュアブートを考慮して、特殊なオプションを２つ渡します。"--modules=tpm --disable-shim-lock"

```sh
# 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` で確認できます)。

- 2. パスワードプロンプトとかを表示するようにする

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)
```

- 3. / のマウント

復号とマウントのため、カーネルパラメータを設定します。
Linux カーネルには、起動時にカーネルパラメータを渡すことができ、起動後は /proc/cmdline で確認することができます。
boot パーティションや root パーティションを暗号化する場合、それらのパラメータを変更する必要があります。
カーネルパラメータは GRUB_CMDLINE_LINUX_DEFAULT で編集できます。

設定できるパラメータは [Linux の公式ドキュメント](https://www.kernel.org/doc/html/latest/admin-guide/kernel-parameters.html)にまとまっていますが、バカみてぇに多いです。
ので、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](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 の設定を書き出て再起動します。

```sh
mkinitcpio -P
grub-mkconfig --output=/boot/grub/grub.cfg
```

成功すれば、起動時にパスワードを聞かれて復号化され、ちょっと待つと GRUB の選択画面が出てくるはずです。
そのまま ArchLinux を選択すれば、もう一度 /boot のパスワードを聞かれ、その後起動できると思います。

### カーネルの起動の改良

無事に起動できたはいいのですが、/boot のパスワードを二回聞かれるのはなぜなのでしょうか。
それは、GRUB は入力されたパスワードをカーネルへ渡す方法を持っていないからです。

この問題を解決する方法は、/ の中に /boot パーティションの boot_keyfile を格納し、二回目はそれを用いて復号化する事です。二回目ではすでに / が使えるので、それが実現できます。
更に、LUKS には鍵を**追加**する仕組みがあるため、一回目のパスワードを平文保存する必要もありません。
どこに boot_keyfile を設置しても良いですが、ユーザーからは読めないようにすることで、万が一侵入されてもリスクを低減できます。例によって途中で作成したファイルなどは shred で消し飛ばしましょう。

今回は /root にします。

```sh
# /dev/part2 の２つ目の鍵の生成
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 が盗まれたとしてもプライバシーは守れる様になりました。
しかし、[悪意あるメイド攻撃](https://ja.wikipedia.org/wiki/%E6%82%AA%E6%84%8F%E3%81%82%E3%82%8B%E3%83%A1%E3%82%A4%E3%83%89%E6%94%BB%E6%92%83)をされた場合、情報が漏洩したりパスワードが漏れたりする可能性があります。

この問題の本質は、攻撃者の持ち込んだイメージが起動できてしまうことです。それを防ぐための仕組みが Secure Boot となります。
しかし Secure Boot をしていても防げない攻撃があります。それは、

1. ブートローダーに署名できる組織が悪意を持っている場合
2. Secure Boot を無効化できる場合

です。それぞれ対処していきます。

## 自分の鍵で署名

1 を対処していきます。
Secure Boot を有効にするにあたり、我々が取れる方法は２つあります。

- 署名済みブートローダーを使う
- 自分で署名する。

出荷される PC には通常 Windows が Microsoft の署名付きでインストールされています。その署名を検証できる様に事前に彼らの持つ公開鍵を持っているのです。
PreLoader や shim は Windows と同じ様に Microsoft によって署名されたブートローダーなので、Secure Boot を有効にしてもそのまま起動できます。

しかしこれは本当に安全でしょうか？Microsoft が鍵の管理を誤り外部に漏らしてしまったら？悪意を持つ攻撃者が Microsoft 自身だったら？

怖いですね。なので今回は 自分で署名する 方法を取ります。そうすることで自分で管理を間違えなければいいだけになります。(却って不安になる気もするけど仕方ない...)

例によって [ArchWiki#セキュアブート](https://wiki.archlinux.jp/index.php/%E3%82%BB%E3%82%AD%E3%83%A5%E3%82%A2%E3%83%96%E3%83%BC%E3%83%88#.E8.87.AA.E5.88.86.E3.81.A7.E7.BD.B2.E5.90.8D.E3.81.97.E3.81.9F.E9.8D.B5.E3.82.92.E4.BD.BF.E3.81.86)に沿って進めていきます。
Wiki にきれいにまとまっているのでそのまま載せます。作業は /root などで行いましょう。

```sh
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` にキーの設定ができるイメージが生成されます。後で使います。

さて、鍵の生成ができたら実際に署名します。

```sh
# 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 アプリケーションとして登録します。

```sh
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 を問題なく起動できたら成功です。
最後に、

```sh
# 攻撃者に 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 に成って行ってください。

```sh
gpg --full-gen-key
```

鍵の種類は RSA and RSA を選びます。GRUB が対応していないのか別の原因があるのかわかりませんが、他の方式では全て失敗しました。
鍵の長さは任意です。

署名を補助してくれるツールとして [grub2-signing-extension](https://github.com/Bandie/grub2-signing-extension) を使います。小さなシェルスクリプトで構成されているので不安な方は確認できます。

信用する鍵を /boot に格納します。

```sh
gpg --export > /boot/gpg.key
```

信用する鍵と、署名確認の強制などを設定ファイルに書き込みます。
/etc/grub.d/ の何処かに以下を追記します

```
insmod gcry_sha256
insmod gcry_rsa
trust /gpg.key
set check_signatures=enforce
```

設定を反映し、署名します。

```sh
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 を使って運用面でもパラノイアになっていきましょう！

おつかれさまでした！

質問や誤字脱字、情報の間違いなどがあればコメントか[私のついったー](https://twitter.com/Iwancof_ptr)にお願いします〜