TPMとは
Trusted Platform Module とは セキュアな暗号プロセッサの国際規格です。
暗号鍵をデバイス内に持つことでセキュアな暗号プロセッサを実現しています。
TPMは通常ハードウェアとして実装されますが、ソフトウェアの実装もあります。TPMの要は暗号鍵がデバイスの中にあることなので、ソフトウェア実装にはあまり利点がなさそうですが、TPMの動作を検証する際には役に立ちます。
なぜTPM?
社内でSPIREを調査・検証することがあったのですが、SPIREはAgentが動作しているマシンのアイデンティティを証明するためにいくつもの方法を用意しています。
その中にはTPMを用いたものもあり、これを使ってみようかな?と思ったのですが、手軽にTPMを使う方法がよくわからなかったので、今回少し調べてみることにしました。
(この記事では、SPIREとTPMを組み合わせるところまでは行かずに力尽きていますが・・・)
ソフトウェアTPMの実装
今回はswtpmを利用してみます。
どうもSPIREからTPMを使っている実装では、/dev/tpmX
にアクセスしているようで、まずはこれをどうやって作るか?ということを調べ始めました。
https://github.com/stefanberger/swtpm/wiki/Using-the-IBM-TSS-with-swtpm に記載されている Character device using tpm_vtpm_proxy
が、その方法のようでしたが、tpm_vtpm_proxy
というカーネルモジュールが必要そうです。
名前にvtpmというのが出てきましたが、これが仮想TPMというやつのようです。おそらくソフトウェア実装のTPMをTPMとして見せるためのプロキシなのでしょう。
説明を読むと containerという言葉が出てくるが、今回は普通にホストから使いたいのだが、目的にあっているのだろうか? ひとまず調査を進めました。
VMで試してみる
OSはVagrantで作ったCentOS8です。
下記のようなVagrantファイルで作りました。
Vagrant.configure('2') do |config|
config.vm.provider :virtualbox do |v|
v.cpus = 1
v.memory = 512
end
config.vm.box = "generic/centos8"
config.vm.box_version = "3.2.20"
config.vm.define "build-vm", autostart: false do |instance|
instance.vm.hostname = "tpm-test"
instance.vm.network :private_network, ip: "192.168.101.2" # IPアドレスは適宜変更してください
end
end
さて、modprobeでロードできるかな?
$ modprobe tpm_vtpm_proxy
modprobe: FATAL: Module tpm_vtpm_proxy not found.
あえなく撃沈。
色々調べるとこれはどうやらカーネルビルド時に設定が必要らしい。
# cat /boot/config-4.18.0-240.22.1.el8_3.x86_64 |grep TPM
CONFIG_TCG_TPM=y
CONFIG_HW_RANDOM_TPM=y
# CONFIG_TCG_VTPM_PROXY is not set
CONFIG_TCG_VTPM_PROXYというのを設定する必要がありそうですが、このカーネルでは設定されていません。
カーネルの再構築
いくつかのサイトを拾い読みしながら、カーネルを再構築しました。
(もっと良い方法があったら教えてください)
参考サイト
- https://qiita.com/tutuz/items/f824475d00b35dd39e6b
- https://tech.pjin.jp/blog/2015/11/14/linux-%E3%82%AB%E3%83%BC%E3%83%8D%E3%83%AB%E3%81%AE%E5%86%8D%E6%A7%8B%E7%AF%89-%EF%BD%9E%E7%AC%AC1%E5%9B%9E%EF%BD%9E/
# 必要なパッケージのインストール
$ sudo yum install rpm-build ncurses kernel-devel bison flex bc openssl-devel
# 現在のカーネルと同じバージョンのソースパッケージを取得
$ wget https://ftp.riken.jp/Linux/centos-vault/8.3.2011/BaseOS/Source/SPackages/kernel-4.18.0-240.22.1.el8_3.src.rpm
# 展開方法がよくわからなかったのでcpioに変換して取りだす。
$ rpm2cpio kernel-4.18.0-240.22.1.el8_3.src.rpm | cpio -id
231928 blocks
# 解凍する
$ xz -dc linux-4.18.0-240.22.1.el8_3.tar.xz | tar xvf -
$ cd linux-4.18.0-240.22.1.el8_3/
# 今のカーネルのconfigを持ってくる
$ cp /boot/config-`uname -r` ./.config
# お作法なのかな?
$ make oldconfig
LEX scripts/kconfig/zconf.lex.c
HOSTCC scripts/kconfig/zconf.tab.o
HOSTLD scripts/kconfig/conf
scripts/kconfig/conf --oldconfig Kconfig
#
# configuration written to .config
#
カーネルのconfigを変更します。
$ make menuconfig
コンソールベースのUIから下記を変更します。
Device Dricvers --->
Character decices --->
TPM Hardware Support --->
VTPM Proxy Interface を[M]にする
Kernel hacking --->
[*] Kernel debuggingをオフにする
で、カーネルをビルドします。(すごく時間がかかります)
$ make
ビルドしたカーネルモジュールをインストールします。
$ sudo make modules_install
$ ls -lah /lib/modules
total 16K
drwxr-xr-x. 5 root root 85 Dec 3 05:01 .
dr-xr-xr-x. 31 root root 4.0K May 11 2021 ..
drwxr-xr-x. 3 root root 4.0K Dec 3 05:02 4.18.0
drwxr-xr-x. 7 root root 4.0K Dec 3 03:17 4.18.0-240.22.1.el8_3.x86_64
drwxr-xr-x. 6 root root 4.0K May 11 2021 4.18.0-240.el8.x86_64
4.18.0というのが今回できたもののようです。
$ find /lib/modules | grep vtpm
/lib/modules/4.18.0/kernel/drivers/char/tpm/tpm_vtpm_proxy.ko
お目当てのカーネルモジュールも存在しています。
次にビルドしたカーネルをインストールします。
$ sudo make install
sh ./arch/x86/boot/install.sh 4.18.0 arch/x86/boot/bzImage \
System.map "/boot"
cp: cannot stat '/boot/bls.conf': No such file or directory
sed: can't read /boot/loader/entries/4cf3b9fc42e040ab80d5666fb3657a40-0-rescue.conf: No such file or directory
make[1]: *** [arch/x86/boot/Makefile:155: install] Error 2
make: *** [arch/x86/Makefile:324: install] Error 2
ややエラーが発生していますが、無視して進みます。
$ ls -lah /boot
total 311M
dr-xr-xr-x. 5 root root 4.0K Dec 3 05:08 .
dr-xr-xr-x. 17 root root 224 May 11 2021 ..
-rw-r--r--. 1 root root 173 Apr 8 2021 .vmlinuz-4.18.0-240.22.1.el8_3.x86_64.hmac
-rw-r--r--. 1 root root 166 Sep 25 2020 .vmlinuz-4.18.0-240.el8.x86_64.hmac
lrwxrwxrwx. 1 root root 23 Dec 3 05:06 System.map -> /boot/System.map-4.18.0
-rw-r--r--. 1 root root 3.9M Dec 3 05:06 System.map-4.18.0
-rw-------. 1 root root 3.9M Apr 8 2021 System.map-4.18.0-240.22.1.el8_3.x86_64
-rw-------. 1 root root 3.9M Sep 25 2020 System.map-4.18.0-240.el8.x86_64
-rw-r--r--. 1 root root 186K Apr 8 2021 config-4.18.0-240.22.1.el8_3.x86_64
-rw-r--r--. 1 root root 186K Sep 25 2020 config-4.18.0-240.el8.x86_64
drwxr-xr-x. 3 root root 17 May 11 2021 efi
drwx------. 4 root root 83 Dec 3 03:20 grub2
-rw-------. 1 root root 76M Dec 3 05:08 initramfs-0-rescue-4cf3b9fc42e040ab80d5666fb3657a40.img
-rw-------. 1 root root 70M May 11 2021 initramfs-0-rescue-c49b328af64d4d1796c972833f1a0e82.img
-rw-------. 1 root root 30M May 11 2021 initramfs-4.18.0-240.22.1.el8_3.x86_64.img
-rw-------. 1 root root 32M May 11 2021 initramfs-4.18.0-240.el8.x86_64.img
-rw-------. 1 root root 18M May 11 2021 initramfs-4.18.0-240.el8.x86_64kdump.img
-rw-------. 1 root root 31M Dec 3 05:07 initramfs-4.18.0.img
drwxr-xr-x. 3 root root 21 May 11 2021 loader
lrwxrwxrwx. 1 root root 20 Dec 3 05:06 vmlinuz -> /boot/vmlinuz-4.18.0
-rw-r--r--. 1 root root 7.6M Dec 3 05:07 vmlinuz-0-rescue-4cf3b9fc42e040ab80d5666fb3657a40
-rwxr-xr-x. 1 root root 9.1M May 11 2021 vmlinuz-0-rescue-c49b328af64d4d1796c972833f1a0e82
-rw-r--r--. 1 root root 7.6M Dec 3 05:06 vmlinuz-4.18.0
-rwxr-xr-x. 1 root root 9.1M Apr 8 2021 vmlinuz-4.18.0-240.22.1.el8_3.x86_64
-rwxr-xr-x. 1 root root 9.1M Sep 25 2020 vmlinuz-4.18.0-240.el8.x86_64
- initramfs-4.18.0.img
- vmlinuz-4.18.0
ができているように見えます。
ここまで来たらシステムを再起動します。
$ sudo reboot
新しいカーネルで起動しました。
$ sudo modprobe tpm_vtpm_proxy
やっとこのコマンドが成功するようになりました。
swtpmのインストール
やっとソフトウェアTPMを動かす準備が整ったのでswtpmをビルドします。
# 必要なパッケージをインストール
$ sudo yum install libtasn1-devel expect socat python3-twisted fuse-devel glib2-devel gnutls-devel gnutls-utils gnutls json-glib-devel libtpms-devel libseccomp-devel
# swtpmのソースをダウンロード
$ curl -LO https://github.com/stefanberger/swtpm/archive/refs/tags/v0.7.0.tar.gz
$ tar xzf v0.7.0.tar.gz
$ cd swtpm-0.7.0/
# ビルド・インストール
$ ./autogen.sh --with-openssl --prefix=/usr
$ make
$ sudo make install
swtpmを起動しvTPM経由でアクセスする
キャラクタデバイスとしてtpmを実行してみます。
# vtpmのカーネルモジュールのロード(すでにロードしている場合は不要です。)
$ sudo modprobe tpm_vtpm_proxy
# swtpmが利用する作業ディレクトリを作成
$ mkdir /tmp/mytpm
# キャラクタデバイスとしてswtpmを起動
$ sudo swtpm chardev --vtpm-proxy --tpmstate dir=/tmp/mvtpm --tpm2 --ctrl type=tcp,port=2322
New TPM device: /dev/tpm0 (major/minor = 10/224)
swtpmは動かし続けたいので、別の端末からVMにログインし、tpmを操作するためのCLIもインストールします。
$ sudo yum install tpm2-tools
NVRAMと呼ばれるTPMのストレージにアクセスして登録されている情報を見てみます。
$ sudo tpm2_getcap handles-persistent
$
おや?空っぽです。
swtpmの初期設定を実施する
通常TPMは工場出荷時にいくつかの情報がここに書き込まれているはずです。
swtpmではこの作業はコマンドラインから実施することができます。
下記にやり方が書かれていたので試してみました。
一度swtpmを停止して下記を実行します。
このコマンドにより ルートCAを作り、それで署名した証明書がTPMに格納されます。
# 一度データを消してやり直す
$ sudo rm -rf /tmp/mytpm
# 改めてディレクトリを作成
$ sudo mkdir /tmp/mytpm
$ sudo chown tss:root /tmp/mytpm
# セットアップ
$ sudo swtpm_setup --tpmstate /tmp/mytpm --create-ek-cert --create-platform-cert --tpm2
再びswtpmを起動します
$ sudo swtpm chardev --vtpm-proxy --tpmstate dir=/tmp/mvtpm --tpm2 --ctrl type=tcp,port=2322
New TPM device: /dev/tpm0 (major/minor = 10/224)
別端末からVMにログインして、NVRAMを見てみます。
$ sudo tpm2_getcap handles-nv-index
- 0x1C00002
- 0x1C00016
- 0x1C08000
先ほどと違い3行出力されました。
マニュアルによると 0x1C00002がEK cert、0x1C08000が Platform certificateとのこと。
EK証明書が正しいものかどうか検証してみる
EK certというのがこのTPM固有の証明書のようです。
この証明書は先程のセットアップ時に生成されたルート証明書で署名されています。
実際にはこのルート証明書は、PCをセットアップしたメーカーが用意するものとなるはずです。
セットアップ時に生成されたルート証明書は /var/lib/swtpm-localca
に格納されています。
これを使ってEK証明書が正しいものかどうかを検証してみます。
EK証明書をNVRAMから取り出します。
$ sudo tpm2_nvread 0x1c00002 > ekcert.der
derファイルから証明書をpem形式で取り出します。
$ openssl x509 -inform der -in ekcert.der -outform pem -out ekcert.pem
swtpm_setupの際に生成されたルートCA証明書、中間証明書を連結する。
(これらは /var/lib/swtpm-localca に置かれている)
cat swtpm-localca-rootca-cert.pem issuercert.pem > bundle.pem
EK証明書を検証してみます。
$ openssl verify -CAfile bundle.pem ekcert.pem
ekcert.pem: OK
確かにセットアップ時に作られたCA証明書で署名されたものであることが確認できました。
終わりに
まだまだ調査は始まったばかり、というところですが、ひとまずここまで記事にしてみました。
TPMやカーネル再構築など、普段やらない操作ばかりなので、このドキュメント自体に誤りが含まれている可能性が多分にあります。もしお気づきの点がありましたら、お気軽にコメントください。
(さらにいうと https://github.com/spiffe/spire/blob/main/doc/plugin_server_nodeattestor_tpm_devid.md を試した方がいれば、その方法を教えて欲しいです・・)