6
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

VTPMを使ってTPMをお試ししてみる

Last updated at Posted at 2021-12-04

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というのを設定する必要がありそうですが、このカーネルでは設定されていません。

カーネルの再構築

いくつかのサイトを拾い読みしながら、カーネルを再構築しました。
(もっと良い方法があったら教えてください)

参考サイト

# 必要なパッケージのインストール
$ 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 を試した方がいれば、その方法を教えて欲しいです・・)

6
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?