aimdevel’s blog

勉強したことを書きます

dm-cryptを使用してラズパイのストレージを暗号化

初めに

前回はdm-verityとoverlayfsを組み合わせて、read onlyなrootfsの一部を描きこみ可能領域に設定した。

aimdevel.hatenablog.com

書き込み可能領域は構築するシステムに応じて自由に設定できて、ユーザ固有の情報やログなども保存できる。
ただし、組み込み機器にユーザ情報などを保存する場合は、特にデータが外部に漏れないようにする必要がある。
そこで、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.

この領域を使えば以下のような流れで暗号化領域を復号するキーファイルをデバイス固有の鍵で暗号化できる気がする。試してないけど。

初回起動時

  1. 初回起動時にOTP領域にランダム値の「デバイス固有鍵」を書き込み
  2. 再起動後に残らない領域(ramディスクなど)にdm-crypt用の適当な「キーファイル」を作成
  3. 「キーファイル」を「デバイス固有鍵」を使って共通鍵暗号で暗号化し、「暗号化キーファイル」を作成。ストレージ上の鍵保存用領域に格納
  4. 「キーファイル」を使用してストレージの暗号化領域をdm-cryptでマッピング、初期化
  5. 念のため、「キーファイル」を削除

2回目以降起動時

  1. ストレージ上の鍵保存用領域の「暗号化キーファイル」を「デバイス固有鍵」で「キーファイル」に復号し、再起動後に残らない領域に一時的に格納
  2. 「キーファイル」でストレージの暗号化領域をdm-cryptでマッピング
  3. 念のため、「キーファイル」を削除

以上のような流れで平文の鍵を保存せずともdm-cryptを使用することができると考えられる。