TPMとの戯れ long version

はじめに

この記事はセキュリティ・キャンプ全国大会 2023のLT大会で発表した以下のスライドを解説するものです. このときは5分程度しかなかったのでまともに解説できなかった. なんか色々書いていたら9000文字を越えてしまったので, 暇なときにつまみ読んでもらえればと思います.

https://elliptic-shiho.github.io/slide/Playing_with_TPM.pdf

この記事は少し砕けた形で書いてみようかと思いますが, 普段硬い文章ばかり書いているので読みづらいかもしれません. その点ご容赦ください. あとやたら長い.

前提としては「認証と認可の違い」「応用情報処理程度のPKIの知識」あたりがあるとわかりやすいと思います.

TPMとは

皆さんTPM使ってますか? Windows 11へのアップグレード時にお世話になるあれです1. TPMという単語はTrusted Platform Moduleのアクロニムで, Windows11で要求される「2.0」は2023年現在の最新バージョンです. 実はTPM 2.0はちゃんとISO規格にもなっています2. 策定はTrusted Computing Group (TCG) が行っており, 著名なチップメーカーやOSメーカーが名を連ねています.

2.0の一個前のバージョンは1.2です. こちらもISO規格となっており3, BitLocker等で既に利用されています.

BitLockerという単語が登場したことで, TPMが暗号に関係することを皆さんは想像したことでしょう. 大正解です. TPMは暗号処理に関するコプロセッサとして登場したものです. 特に「安全な乱数生成」「TPMチップ固有の情報を用いた所有者認証」あたりが目玉でしょうか. TPM2.0は更に複雑になっているのですが, そのあたりの詳細はそのうちまた別に記事を書くこととします.

TPMは暗号処理全般を担当できますから, 「TPMを用いることによって, 暗号に関する処理をCPU上で行う必要がなくなる」というメリットもあります. 耐タンパ性より「安全に鍵を保存できる」という性質もあるといたれりつくせりですね. 当然TPM内から秘密鍵を持ち出せないようにするオプションもあり, これを用いることで「当該TPMをもっていない場合秘密鍵の復元はできない」というような状況を容易に作り出せます.

ハードウェア編

そんなTPM, ぜひともお話してみたいと思いませんか? ここでは思うことにしておいてください. この手の部品は秋月や千石ではまず手に入りませんので, 初手でDigi-keyを見に行きます.

調べると結構出てくるもので, アクティブな製品に限ってもInfineon, Microchip, STMicroelectronicsと著名なメーカーの製品が100件ほどヒットします.

今回は手元にだぶついていたMicrochip MCP2221A (USB - I2C / UART / GPIOコンボという便利なチップ)を使いたかったため, 通信手段はI2Cに限定します. そしてMicrochip / Atmelの製品は細かいデータシートが公開されていないため4に一旦除外. 当然バージョンは2.0固定. 絞っていくと案外製品は少なくなるもので, 最終的にはInfineon社のSLB96735というチップに収まりました. 購入当時は1チップ828円でした.

次は回路設計です. 今回はハードウェアの話をしたいわけではないので, 詳細は省きます. 実は私はプログラミングに触れるより前から回路設計をしています.

シンプルにつないだだけで, それ以外はSLB9673のデータシートにある推奨回路とUSB回りの設計からESD対策を抜いたぐらいです. 良い子の皆さんはちゃんと入れましょう. 一点もののつもりだったので, 完成品は以下のとおりユニバーサル基板製です.

0.5mmピッチのQFNはそこまではんだ付けしにくくなく, どれかといえばmicroUSB端子へのはんだ付けの難易度のほうが高かった. あれはすぐ外れてしまう...

ソフトウェア編 - MCP2221A

今回はMCP2221AのUSB-HIDデバイスドライバをRustで自作しています. その要約をさらっと書き記します.

当初MCP2221AのドライバがLinux kernelに取り込まれていると聞き喜んでそちらを使っていたのですが, 実はこの実装はいくらかバグっています. 具体的にはI2C通信がたまにストールします. そもそもLinux専用ですので, MacWindowsでは動きません. それではUSBドングルとしての汎用性に欠けてしまいます. ということで既存実装を探しました. そのうちgoogle/mcp2221-rsZakKemble/libmcp2221をここで取り上げます.

まず前者はRustとlibusbによる実装で, 最初から開発に利用していたものです. しかしlibusbは新し目のMacと相性が悪く6, カフェなんかに出ておしゃれに7TPMプロトコル・スタック開発をやろうとしていた私の用途には合いませんでした.

後者はC言語による実装ですので, Rustで使う場合FFIを書かないといけません. 今回Unsafe Rustは可能な限り扱いたくなかったので, この選択肢も結局除外されます.

最終的に, これなら作ったほうがいいやとエイヤで書きました. USB-HIDレイヤまではhidapi任せなのでそこまで難しくありません. 普通のデバドラ開発と同じですね.

ソフトウェア編 - TCTI

TPMの仕様書は4分冊となっていますが, 実はこれは不完全です. というのも, この仕様書では「TPMに対するコマンド体系とその基本思想」は完璧に定義されているのですが, 「具体的にどう送るのか」に関しては一切定義がないのです.

当然これは意図的なもので, TCGはその下位レイヤーをTPM Command Transmission Interface (TCTI) と称し, 様々なインターフェイスごとに別個に規格書を出しています. I2C, SPI, LPC, TCPといったインターフェイスそれぞれについてです.

特にPC関連のTCTIに関する記述は TCG PC Client Platform TPM Profile Specification for TPM 2.0 - https://trustedcomputinggroup.org/resource/tcg-tpm-i2c-interface-specification/物理層からの全てが記載されています. プルアップ抵抗値やデバイスアドレス, チップ形状ごとのピンアサインまで細かく規定されており, これを読んでいればメーカーのデータシートは不要なのではと思わせるほどです.

また, TCG PC Client Device Driver Design Principles for TPM 2.0 - https://trustedcomputinggroup.org/resource/tcg-pc-client-device-driver-design-principles-for-tpm-2-0/ という素晴らしいドキュメントのおかげで, 我々はTCTIレイヤに関して詰まることなく実装を進められます. 実はTPM 2.0の参考書というものはほぼ見当たらないのですが, その一因はこの親切な定義にあるのではとちょっとばかり思っています.

tpmGoフラグを1にし忘れていたがために動作しない, MCP2221A側のデバドラがバグっていたなどの困難はありましたが, ここまでを実装してようやくTPMとお話できるようになります.

ソフトウェア編 - TPM API

TPM自体の仕様書はTrusted Platform Module Libraryという名前で総称されており, それは以下の4冊からなります. この4冊は合計でも2000ページほどしかなく, Intel SDMやTegraの仕様書を読み慣れた低レイヤーの民としては短くて感謝の念が起きるほどです.

  1. Part 1: Architecture (TPM 2.0のアーキテクチャ・理念)
  2. Part 2: Structures (TPM 2.0に関する構造・型)
  3. Part 3: Commands (TPM 2.0に搭載されているコマンド)
  4. Part 4: Supporting Routines (TPM 2.0の仕様の利用に関する情報8)

しかしそれでも問題は起きます. たとえばTPMTPM2_Startup というコマンドを実行することでスタートアップ処理を実行するのですが, 「このコマンドを実行するために何をどうエンコードして送ればいいのか」に関する直接的な情報はどこにもありません. 要するに, 「この4分冊をどう読めばいいのか」という段階で詰まってしまうのです.

理解した今であれば「Part 1でコマンド実行体系を掴み, Part 3でコマンドごとのパラメータリストをチェックした上で, Part 2に書かれている構造体に格納・エンコードする」と言えるのですが, 読み始めた当時はそんなことはわかりません.

色々と探した結果, 入門書として公開されている A Practical Guide to TPM 2.0: Using the Trusted Platform Module in the New Age of Security という書籍の第五章 "Navigating the Specification" に行き着きました. この章はTCP経由で利用できるTPMのシミュレータを利用し, 実際に仕様書のどこを読めばいいのかに関する読み方を提供するもので, これをもとに試しにいくつか手で作ったコマンドを送って確かめることで読み方を学びました.

とりあえず TPM2_Startup を実行できたら, 次は TPM2_SelfTest を実行できるように. 今度は TPM2_GetRandom で乱数を...といった形で実装し, 後は必要な構造体を必要なときに作るだけでよいところまで来ました.

実装に悩んだ箇所として, TPM2にはinterface typeという特殊な型があります. これは「TPMチップに依存して実装有無が変わるもの」という認識でよいです. Rustで書いている都合上, 動的にenumを変化させるのは難しいですし, かといって定数化してしまうのはちょっとどうかなというところ. 結局subenum crateを使って「実装されているかどうかはユーザーの責任として確認」「基本interface typeは部分型として実現」という形に収めました.

また, interface typeの最大の活用例である「暗号アルゴリズムの実装状況によって変化する型」には一番悩まされました. TPM2の仕様書には !ALG.ax のような謎の表記があります. これは「任意の非対称かつ署名に利用できるアルゴリズム」と読みます. そして !ALG.AX になると「任意の非対称かつ署名に利用できるアルゴリズムで, 特にそれ以外の用途に使えないもの」と読みます. TPMの世界においては暗号アルゴリズムにはa(非対称)やx(署名)といった属性が付与されており, これらを選択するためのものです. 以下に Trusted Platform Module Library Part 2: Structures p.93 §9.33 TPMI_ALG_SIG_SCHEMEより例を引用します:

これをsubenumで実装することに私は負けてしまい, だいぶ煩雑な形で実装することになってしまいました. いまでもこれをどう書くときれいに収まるのかに関しては悩み続けています. どなたかいい案あったら教えてください: https://github.com/ssh-slb9673-playground/tpm-ssh-agent/blob/b5e5aef420cfede64592fe19668830669a72254c/tpm_i2c/tpm/structure/algorithm/identifiers.rs

ソフトウェア編 - SSH Agent

せっかく単体で動くTPMボードがあるのですから, なにか実用的な目的を設定したいです. ということでSSH Agent9を作りました. といっても中身は殆ど既存crate( wiktor-k/ssh-agent-lib )を利用したもので, 私が書いたのはTPMとの通信処理と鍵生成ぐらいです.

なお, 私の書いたコードは TPM2_CreatePrimary を利用したもので, 特に TPM2_EvictControl による永続化をしているわけではないため, 再接続時に鍵は失われます. それでもちゃんと接続はできました. 「特定のハードウェアをもっていなければ接続できないSSHサーバー」の完成です.

ただし, プライマリキーの生成方法の都合上, 実はここにはセキュリティ的な課題があります. この点はTPMアーキテクチャに深く関わるものですので, 皆さんも是非この「CreatePrimaryが作る鍵は一体なんなのか」から仕様書と仲良くなってみてはいかがでしょう?

おわりに

今回書いたコードは https://github.com/ssh-slb9673-playground/tpm-ssh-agent にあります. 開発期間は概ね2週間程度, キャンプ終了時点の行数は4700行程度とそこそこの大きさになりました. いい勉強にはなったのではないでしょうか.

TPM 2.0はあまり国内のアマチュアが触れていない領域ということもあり, 今後何かしらこの分野の情報発信ができたらいいなあなどと考えています. USBは皆仕様書を読んでいるのに, TPM 2.0は誰も読んでいない. 不公平.

現時点ですぐに触れられる情報源のうち最も有名なのは Will Arthur, David Challener, and Kenneth Goldman. 2015. A Practical Guide to TPM 2.0: Using the Trusted Platform Module in the New Age of Security. Apress Berkeley, CA. だと思います. これは無料公開もされているため, 皆さんもすぐに読み始めることができます: https://link.springer.com/book/10.1007/978-1-4302-6584-9

また, Trusted Computing全体を通観するために私が読んだ本として Graeme Proudler, Liqun Chen, and Chris Dalton. 2014. Trusted Computing Platforms: TPM2.0 in Context. Springer. を挙げておきます. というかこの2冊以外にすぐに手に入る範囲のTPM 2.0の解説書はありませんでした. 本書は実務でTPM関連の開発をする人にも有用と思われますが, 些かアーキテクトに寄っている感もあります. TPM1.2とTPM2.0の差分に関する記述もありますので, 研究の際にもよい資料となるでしょう.

情報源が少ない理由はいくつかありますが, そのうち1つは間違いなく「十分に使いやすいプロトコルスタックの提供がなされている」という点にあるでしょう. たとえば https://github.com/tpm2-software/tpm2-tsshttps://github.com/microsoft/TSS.MSR です. 自作する必要性がない上, この中身に触れるような人は間違いなく仕様書を読めます. つまり解説の必要がない.

単純に「TPM 2.0はできることが多すぎる」という点も挙げられます. 今のTPM 2.0はPCRバンクを用いた起動時の整合性検証(所謂secure boot)の他, 「TPMに対して発行したコマンド列とその結果が間違いなくこちらの指示通りであることを確認しつつ, その実行されているTPMが本当に我々の指定したTPMであるか否かを識別する」とか, 「OSから特定の条件を与え, 起動ステートが一定の状態になっていなければ復号できない秘密鍵」みたいなものを作ることもできます.

上記事項に関してはPCR, ポリシー, remote attestation, audit sessionで調べてみると魔境が見えるのでおすすめです. 当然CPU - TPM間の通信を暗号化することもできますし, 生成した秘密鍵にはAuthValueなる個別のパスワード的なものを設定できます. TPM内のオブジェクトの認可制御だけで一冊書けるのではないでしょうか.

SSH agentにする際並行実行可能にする必要に気づき, そこからほぼ手を付けていなかったRustでの並行プログラミングをやるようなこともありましたね. tokio + reqwest程度なら触っていたけど, ちゃんと構造体にSync書いたのは初めてに思います.

最近実は少しアップデートを挟んでおり, Remote AttestationのProof-of-Conceptを実装してあります. 回路図のpdfも置いてあるので, もし必要であればご利用ください.

Future work

勢いで書いただけあってコードがかなり汚いです. もう少し真面目にクラス図描いた方が良かった気がします. ということで来年以降もう少し行儀の良いTPMプロトコル・スタックを作りたいと思っています.

TPM 2.0は認可制御が本当に複雑で, その上TPMにおけるセッションと認可の間の包含関係はベン図がないと理解できない状態です (A Practical Guide to TPM 2.0 p.168 Figure 13-1. Authorizations and sessions Venn diagram にその図があります). このあたりをきれいに見通せるよう組み直す試みだけで2ヶ月ほど経過してしまった.

私が今回調べた限り, アカデミア領域では研究対象になっているTPM 2.0も, アマチュアで楽しむには情報源が少ない状況です. 時間に余裕があれば, セキュリティモデルの概説や各種技術詳細を読む記事を書いていくのもありなのかなと夢想はしています. どこにそのような時間が?

おまけ

私は一点もののつもりだったのですが, このUSBドングルはどうせならもっとドングルらしくしたいなとは思っていました. チップも余っていますし...

そこで, 昨年のキャンプ受講生だったsaitenさん ( @sort_reverse )はそういえばプリント基板作ってたじゃんとお願いしてみたところ, USBドングル然としたTPM基板が出来上がりました. 大変感謝しています (いつも面倒だからPCB設計やらないのです...).


本記事は@leno3sさんに軽くレビューしてもらいました. ありがとうございます.


  1. 実は私はWindows10登場後ちょっとしてからWindowsを見限ってしまい, この経験はありません... 2023年末になってもオフラインの検証機はWindows7です.
  2. ISO/IEC 11889:2015
  3. ISO/IEC 11889:2009
  4. NDA必須. なお通信プロトコルやピンアサインはTPM仕様側で決まっており, 特殊なことをしない限りは利用可能.
  5. 当時はアクティブステータスだったのですが, 今見ると最終購入期限が設定されてしまっています. SLB9670の上位機種としてはあまり売れなかったのでしょうか... (I2CでTPMと通信する需要自体があまりなかった...?)
  6. https://github.com/libusb/libusb/issues/1014
  7. カフェに居るぐらいしか時間がなかったとも言う
  8. 実はPart 2, Part 3は機械可読であるように書かれており, 手実装する必要はありません. そのためのフォーマット情報や, 環境依存のパラメータリストの既定値等が収録されています.
  9. SSHの公開鍵認証を代行するデーモン. 便利.