初めに
前回はdm-verityとoverlayfsを組み合わせて、read onlyなrootfsの一部を描きこみ可能領域に設定した。
書き込み可能領域は構築するシステムに応じて自由に設定できて、ユーザ固有の情報やログなども保存できる。
ただし、組み込み機器にユーザ情報などを保存する場合は、特にデータが外部に漏れないようにする必要がある。
そこで、dm-cryptでパーティションの暗号化を試す。
dm-cryptについて
ブロックデバイスを暗号化するLinuxの機能。
cryptsetupというツールを使う。使い方は以下のサイトを参照。
https://gitlab.com/cryptsetup/cryptsetup/-/wikis/DMCrypt
https://www.man7.org/linux/man-pages/man8/cryptsetup.8.html
これを使うことによってsdカードやemmcのパーティションを暗号化することができる。
暗号化パーティションはパスワードかキーファイルで保護される。マウントまで自動化したいのでキーファイルを使用するのがよいと考えられる。
そのままだとキーファイルの内容が流出すると暗号化領域を復号されてしまうので、キーファイル自体を暗号化して保護するなど、何らかの対策が必要。
作るもの
以下の流れで起動して、書き込み可能領域を暗号化する。
1. initramfsのinitスクリプトを起動
2. initramfsに含めたroot hashと別のパーティションに格納したhash treeを用いてrootfsの入っているパーティションを検証
3. device-mapperでread onlyのデバイスとしてrootfsの入っているパーティションが認識されるので、適当な場所にマウント
4. dm-cryptで暗号化パーティション作成。さらに初回起動時のみext2でフォーマット。
5. device-mapperで暗号化パーティションが認識されるので、書き込み可能で適当な場所にマウント
6. OverlayFSで書き込み用ディレクトリをマウント
7. switch_rootでrootfsを切り替え
8. ラズパイOSのrootfsで起動
パーティション構成
ラズパイから見える名前で記載する。
name | size | format | description |
---|---|---|---|
mmcblk0p1 | 510MB | fat32 | ブートパーティション。u-bootやLinux kernelなどを格納 |
mmcblk0p2 | 2GB | ext4 | rootfs。この領域をdm-verityで検証する |
mmcblk0p3 | 200MB | none | dm-verity用のhash tree置き場 |
mmcblk0p4 | none | none | 拡張パーティション |
mmcblk0p5 | 2GB | ext2 | 書き込み用暗号化領域。初回起動時にdm-cryptで暗号化する |
mmcblk0p6 | 32MB | ext4 | dm-crypt用ファイル置き場。キーファイルや初回起動フラグなど。 |
今回のdm-cryptの構成は、暗号化する領域と生のキーファイルが同じsdカードに入っている。
そのため、sdカードをそのまま持ち去られると暗号化領域を復号することができてしまい、セキュリティ的に問題がある。
実際に製品でdm-cryptを使用する際には、キーファイルをデバイス固有の情報で暗号化するなどの対策が必要。
例えば、i.MXではSoCのセキュリティ機能で、SoCの個体ごとに固有の情報+専用のHWを使用して鍵を暗号化することで、ストレージを持ち去られたとしても暗号化領域を複合されないようになっている。
手順
例によって前回作成したsdカードイメージを改造していく。
sdカードイメージに鍵ファイルを追加
initramfsから触れるところにdm-crypt用のキーファイルを追加する。
今回は新しくパーティションを作成しそこにファイルを格納する。
まずパーティション6を追加して、適当なキーファイルを作成する。
fdiskでパーティションを追加して、
sudo fdisk 2024-03-15-raspios-bookworm-arm64-lite.img
ext4でフォーマットし、
losetup -f sudo losetup -P /dev/loop0 2024-03-15-raspios-bookworm-arm64-lite.img sudo mkfs.ext4 /dev/loop0p6
適当なところにマウントして鍵ファイルを作成。
mkdir tmp sudo mount /dev/loop0p6 tmp sudo dd bs=512 count=4 if=/dev/urandom of=tmp/dm-crypt-key sudo umount tmp sudo losetup -f /dev/loop0
initスクリプトを修正
initスクリプトを以下の内容にしてFITImageに組み込む。
FITImgaeの作り方は過去の記事にあるので割愛。
スクリプトの変更点はdm-crypt関連のみ。
初回起動時に、暗号化対象の領域をdm-cryptでマッピング、フォーマットし、必要なディレクトリを作成する処理を行っている。
#!/bin/sh VERITY_ROOTHASH=3d203e1917909149841a8465e71a50f23e27d8b5e191ebea04b90ba442fe6891 mount -v --bind /dev /dev mount -v --bind /dev/pts /dev/pts mount -vt proc proc /proc mount -vt sysfs sysfs /sys mount -vt tmpfs tmpfs /run mdev -s echo "start switch_root!" # parse cmdline cmdline=$(cat /proc/cmdline) for param in $cmdline; do echo $param case $param in root=*) ROOT=`echo $param | sed 's/.*=//'` ;; esac done # wait rootfs for i in {1..30} do /sbin/mdev -s TMP=`ls -l /dev/ | grep $ROOT` if [ -n "${TMP}" ];then break fi echo "waiting rootfs..." sleep 1 done # veritysetup veritysetup open $ROOT verity-root /dev/mmcblk0p3 $VERITY_ROOTHASH # mount rootfs. mkdir -p /dev/root mount -o noload /dev/mapper/verity-root /dev/root # cryptsetup mkdir -p /dev/rw-partition mkdir -p /dev/key-part mount /dev/mmcblk0p6 /dev/key-part cryptsetup --type plain -q -d /dev/key-part/dm-crypt-key -s 128 --hash sha256 -c aes-cbc-essiv:sha256 open /dev/mmcblk0p5 crypt-partition if [ ! -e "/dev/key-part/dm-crypt-flag" ];then # initial setup at first boot touch /dev/key-part/dm-crypt-flag mke2fs /dev/mapper/crypt-partition mount /dev/mapper/crypt-partition /dev/rw-partition cd /dev/rw-partition/ mkdir home var etc overlayfs-work cd /dev/rw-partition/overlayfs-work/ mkdir home var etc cd umount /dev/rw-partition fi umount /dev/key-part # mount rw partition mount /dev/mapper/crypt-partition /dev/rw-partition # mount overlayfs mount -t overlay overlay -olowerdir=/dev/root/home,upperdir=/dev/rw-partition/home,workdir=/dev/rw-partition/overlayfs-work/home /dev/root/home mount -t overlay overlay -olowerdir=/dev/root/var,upperdir=/dev/rw-partition/var,workdir=/dev/rw-partition/overlayfs-work/var /dev/root/var mount -t overlay overlay -olowerdir=/dev/root/etc,upperdir=/dev/rw-partition/etc,workdir=/dev/rw-partition/overlayfs-work/etc /dev/root/etc # switch root exec switch_root -c /dev/console /dev/root /sbin/init
作成したFITImageをsdカードイメージに格納したら、ラズパイ実機で動作確認する。
以下の作業はラズパイ実機で行う。
u-boot設定
setenv bootargs "coherent_pool=1M 8250.nr_uarts=1 root=/dev/mmcblk0p2 rw rootwait" setenv bootcmd "fatload mmc 0:1 0x1000000 FITImage;bootm 0x1000000;" saveenv
起動後の確認
起動時に以下のログが出ているので暗号化に成功しているように見える。dm-1がdm-cryptでマッピングしたデバイスである。ちなみにdm-0はdm-verityで検証してマッピングしたもの。
[ 10.595800] EXT4-fs (dm-1): mounting ext2 file system using the ext4 subsystem [ 10.632345] EXT4-fs (dm-1): mounted filesystem 2bb6f813-7e28-4511-8e7a-6ba6fd21a69c r/w without journal. Quota mode: none. [ 11.064347] EXT4-fs (dm-1): unmounting filesystem 2bb6f813-7e28-4511-8e7a-6ba6fd21a69c.
また、ラズパイOSのrootfsで起動後に、コンソールでdmsetupコマンドを使用して以下のように確認できる。
pi@raspberrypi:~$ sudo dmsetup ls crypt-partition (254:1) verity-root (254:0)
終わりに
dm-cryptを使用して書き込み可能領域を暗号化した。
キーファイルがsdカードに平文で保存されている問題を放置したが、これは使用するSoCごとに異なる対応が必要だと考えられるので、これ以上深くは触れない。
今回の対応で、データ保存領域を暗号化したので、(キーファイル何らかの方法で暗号化できれば)ストレージを抜き取られてもその内容を読み取られることはなくなった。
(おまけ)ラズパイのデバイス固有鍵について
「終わりに」まで書いた後でラズパイのデバイス固有鍵が気になったので少し調べた。
少し上のほうで、製品でdm-cryptを使用する場合は、キーファイルをデバイス固有の情報で暗号化するなどしたほうがいいと書いたが、ラズパイにはデバイス固有の鍵を格納する領域が存在するらしい。
以下のサイトの「Industrial use of the Raspberry Pi」の部分の「One-time programmable settings」のホワイトペーパーの「Device-specific private key」に概要が書かれていた。
https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#industrial-use-of-the-raspberry-pi
これによると、ラズパイはファイルの暗号化などに使用できるように、デバイス固有の秘密鍵を持っている様子。
Eight rows of OTP (256 bits) are available for use as a device-specific private key. This is intended to support file-system encryption, and should be used in combination with the signed boot system to ensure key safety.
以下のスクリプトで値を参照できるらしいので試してみたところ、値は0だった。ユーザが自分で書き込んで使用する想定らしい。
https://github.com/raspberrypi/rpi-eeprom/blob/e430a41e7323a1e28fb42b53cf79e5ba9b5ee975/tools/rpi-otp-private-key
また、ホワイトペーパーには以下の記述もあり、この領域は鍵の格納以外の用途に使用してもいいとのこと。256bitあるからいろいろなことに使えそう。
If secure-boot / file-system encryption is not required, then the device private key rows can be used to store general-purpose information.
この領域を使えば以下のような流れで暗号化領域を復号するキーファイルをデバイス固有の鍵で暗号化できる気がする。試してないけど。
初回起動時
- 初回起動時にOTP領域にランダム値の「デバイス固有鍵」を書き込み
- 再起動後に残らない領域(ramディスクなど)にdm-crypt用の適当な「キーファイル」を作成
- 「キーファイル」を「デバイス固有鍵」を使って共通鍵暗号で暗号化し、「暗号化キーファイル」を作成。ストレージ上の鍵保存用領域に格納
- 「キーファイル」を使用してストレージの暗号化領域をdm-cryptでマッピング、初期化
- 念のため、「キーファイル」を削除
2回目以降起動時
- ストレージ上の鍵保存用領域の「暗号化キーファイル」を「デバイス固有鍵」で「キーファイル」に復号し、再起動後に残らない領域に一時的に格納
- 「キーファイル」でストレージの暗号化領域をdm-cryptでマッピング
- 念のため、「キーファイル」を削除
以上のような流れで平文の鍵を保存せずともdm-cryptを使用することができると考えられる。