【ITニュース解説】From Sending Raw TPM Commands to Parsing Responses with Windows TBS
2026年09月18日に「Dev.to」が公開したITニュース「From Sending Raw TPM Commands to Parsing Responses with Windows TBS」について初心者にもわかりやすく解説しています。
ITニュース概要
WindowsのセキュリティチップTPM 2.0をRustで直接操作する手法を解説。生のコマンドをTBS経由で送信し、その応答を解析するまでの一連の流れを、乱数取得を例に手順を追って説明する。TPMの低レベルな制御を学ぶのに役立つ。
ITニュース解説
TPM(Trusted Platform Module)とは、パソコンなどの電子機器に内蔵されているセキュリティチップのことだ。このチップは、暗号鍵を安全に保管したり、ランダムな数値を生成したり、デバイスの起動状態を検証したりするなど、様々なセキュリティ機能を提供する。Windows環境でTPMを利用する一般的な方法は、TPM Base Services(TBS)というサービスを経由することだ。TBSは、アプリケーションとTPMの間を取り持ち、TPMへのコマンド送信やレスポンスの受信といった通信を仲介する。この記事では、このWindows TBSを使って、TPMに直接「生(raw)」のコマンドを送り、その応答を解析する一連のプロセスを、Rust言語での実装例を交えながら詳しく解説する。システムエンジニアを目指す初心者でも、TPMの基本的な動作原理と、低レベルな通信がどのように行われるのかを理解できるよう、基礎から説明する。
TPMとやり取りする全体の流れは、大きく分けて「通信環境の準備」「コマンドの準備」「コマンドの送信」「レスポンスの解析」の四段階だ。具体的には、まずTPMとの通信を管理するための「TBSコンテキスト」という窓口を作成する。次に、TPMに実行させたい処理を記述した「コマンド」を、TPMが理解できるバイト列の形式に整形する。この整形作業を「マーシャリング」と呼ぶ。マーシャリングされたコマンドはTBSを通じてTPMに送信され、TPMからの「レスポンス」を受け取る。最後に、受け取ったレスポンスのバイト列を解析し、必要な情報を抽出する。今回の例では、TPMが生成するランダムなバイト列を取得する「TPM2_GetRandom」というコマンドを使う。このコマンドは、TPMが提供する機能の中でもセッション管理が不要で、必要なパラメータも少ないため、TPMとの低レベルなやり取りを学ぶには最適な出発点となる。
まず最初に行うのは、TBSコンテキストの作成だ。これは、TPMとの通信を確立するための初期設定のようなもので、後続のコマンド送信で必要となる。Rust言語でWindowsのシステムAPIを呼び出すには、「windows-sys」というクレート(ライブラリ)を使用する。このクレートには、TBSコンテキストを作成するための「Tbsi_Context_Create」という関数が含まれている。この関数を呼び出す際には、TPMのバージョンを指定する必要がある。現在主流のTPMはバージョン2.0であるため、パラメータとして「TPM_VERSION_20」を指定してコンテキストを作成する。関数が成功すると、その後の通信で使うための「ハンドル」と呼ばれる識別子が返される。このハンドルを使って、コマンドの送信やコンテキストのクローズを行うことになる。もしコンテキストの作成に失敗した場合は、エラーコードが返され、それ以上の処理はできない。
次に、TPMに送るコマンドを「マーシャリング」する。これは、プログラム内で扱っているデータをTPMが理解できる特定のバイト列の形式に変換する作業だ。TPMコマンドやレスポンスでは、数値データは常に「ビッグエンディアン」というバイト順でエンコードされる。これは、複数のバイトで構成される数値データにおいて、最も値の大きいバイト(上位バイト)が先頭に配置される形式のことだ。例えば、一般的なPCのCPUはリトルエンディアンでデータを処理することが多いため、TPMと通信する際には必ずこのビッグエンディアン形式に変換する必要がある。 TPMコマンドは、常に10バイトの共通ヘッダーから始まる。このヘッダーには、「tag(タグ)」「commandSize(コマンド全体のサイズ)」「commandCode(実行したいコマンドの種類)」が含まれる。今回のTPM2_GetRandomコマンドはセッションを必要としないため、tagには「TPM_ST_NO_SESSIONS」という値を設定する。commandSizeは、このヘッダー自体とそれに続く全てのパラメータを含めた、コマンド全体のバイト数を示す。TPM2_GetRandomコマンドの場合、取得したいランダムバイト数を指定する2バイトのパラメータのみが必要なので、commandSizeはヘッダーの10バイトにこの2バイトを加えた12バイトとなる。commandCodeには、TPM2_GetRandomコマンドに対応する固有の数値(0x0000_017B)を設定する。これらのヘッダー情報と、取得したいランダムバイトの数をビッグエンディアンのバイト列に変換し、順番に結合することで、完全なコマンドのバイト列が完成する。
コマンドが準備できたら、いよいよTBSを通じてTPMに送信する。これには「Tbsip_Submit_Command」という関数を使用する。この関数を呼び出す際には、作成済みのTBSコンテキストのハンドル、送信するコマンドのバイト列、その長さなどを引数として渡す。また、「Locality(ローカリティ)」と「Priority(優先度)」も指定する。LocalityはTPMがアクセスできる環境を示すもので、通常は「TBS_COMMAND_LOCALITY_ZERO」という値が使われる。Priorityはコマンドの実行優先度を指定するもので、今回の単一コマンドの送信であれば「TBS_COMMAND_PRIORITY_NORMAL」で十分だ。重要なのは、TPMからのレスポンスを受け取るためのバッファを事前に用意しておくことだ。TPMからのレスポンスの最大サイズを予測し、その分のメモリを確保しておく必要がある。TPM2_GetRandomコマンドのレスポンスは、取得したランダムバイトのサイズを示す2バイトと、実際のランダムバイト列で構成される。したがって、最大レスポンスパラメータサイズは「2バイト+要求したランダムバイト数」として計算される。コマンド送信が成功すると、実際にTPMから受け取ったレスポンスのバイト列が、用意したバッファに書き込まれ、その実際の長さが返される。
TPMからレスポンスを受け取ったら、次にそのバイト列を解析して、必要な情報を抽出する。レスポンスの解析も、コマンドのマーシャリングと同様に、ビッグエンディアンのバイト列を元の数値に戻す作業から始まる。TPMからのレスポンスも、常に10バイトの共通ヘッダーで始まる。このヘッダーには、「tag(タグ)」「responseSize(レスポンス全体のサイズ)」「responseCode(応答コード)」が含まれる。コマンドが成功した場合のtagは、コマンド送信時に指定したtagと同じ値となり、responseCodeは「TPM_RC_SUCCESS」(0x0000_0000)となる。もしエラーが発生した場合は、tagは「TPM_ST_NO_SESSIONS」となり、responseCodeにはエラーを示す固有の数値が格納される。エラーの場合、TPMからはパラメータが返されないため、responseCodeを確認することは非常に重要だ。 TPM2_GetRandomコマンドが成功した場合のレスポンスパラメータは、「TPM2B_DIGEST」という形式のランダムバイト列のみだ。TPM2B_*という形式は、データそのもの(バッファ)の前に、そのデータの長さを表す2バイトのサイズ情報が先行するという構造を持つ。したがって、レスポンスを解析する際は、まず2バイトのサイズ情報を読み取り、そのサイズ分のバイト列をデータとして読み込む、という手順を踏む。これにより、実際にTPMが生成して返してきたランダムバイト列を取得できる。なお、responseSizeはレスポンス全体のサイズを示すため、実際のプログラムではこの値を使って、受け取ったデータが正しいサイズであるか検証することが望ましい。特に、可変長のレスポンスを扱う場合には、この検証は非常に重要となる。
一連のTPMとのやり取りが完了し、TBSコンテキストが不要になったら、忘れずにそのコンテキストをクローズする必要がある。これは、最初に作成した窓口を閉じる作業に相当する。TBSコンテキストのクローズには「Tbsip_Context_Close」という関数を使用する。この関数に、最初に作成して取得したハンドルを渡すことで、コンテキストが解放され、関連するシステムリソースが適切に返却される。これにより、メモリリークなどの問題を防ぎ、システムを安定して動作させることができる。
今回の実装は、Windows上でTPMと直接低レベルなコマンドでやり取りする初めての試みとして行われた。このようなバイナリプロトコルを実装する作業は、TPMの仕様書を参照しながら必要なデータ型を定義し、バイト列の形式を正確に変換する必要があるため、想像以上に時間がかかることが多い。特に、コマンド送信からレスポンス解析までの一連の流れを、たった一つのコマンドに対してでも完全に実装するのは骨の折れる作業だ。今回扱ったTPM2_GetRandomコマンドはセッションが不要でパラメータも少ないため比較的シンプルだが、TPMには「HMACセッション」や「ポリシーセッション」など、より複雑なセッション管理を伴うコマンドも存在する。これらのセッションが関わると、コマンドやレスポンスの構造が大幅に複雑化し、実装の難易度が格段に上がる。もし、あなたが初めて生のTPMコマンドを実装しようと考えているなら、今回の例のように、セッションを必要とせず、パラメータも少ないシンプルなコマンドから始めることを強く推奨する。そうすることで、TPMとの低レベルな通信の基礎をしっかりと理解し、段階的に複雑な機能へと進んでいけるだろう。