PSA APIs 16 · TF-M での実装 — 壁の向こうで PSA を動かす

Chapter 16

TF-M での実装 — 壁の向こうで PSA を動かす

この章のゴール.

TF-M の構造(SPE / NSPE、SPM、パーティション、ベニア)を理解し、 非セキュア側から PSA API を呼ぶときに何が起きているか、 ビルドの主要オプション、そして TF-M 固有のエラー(PROGRAMMER_ERROR、メモリ検査、クライアント ID)を説明できるようになること。

この章で使う既出の用語(定義は各リンク先). セキュア領域(01 章 3 節)、ビット(02 章 4 節)、type(03 章 11 節)、import(04 章 10 節)、対称暗号(07 章 1 節)、認証(09 章 1 節)、ITS(13 章 1 節)、IAK(14 章 4 節)、Profile(14 章 3 節)、TF-PSA-Crypto(15 章 1 節)

1. TF-M とは

TF-M(Trusted Firmware-M)は、Arm が主導する Cortex-M 向けセキュアファームウェアの参照実装である。 PSA Certified の「PSA-RoT」(PSA Root of Trust、信頼の起点となる最小限のセキュアなソフトウェア)をそのまま形にしたもので、 PSA Functional API のサーバ側——Crypto、ITS、PS、Initial Attestation、Firmware Update、Platform の各サービス——を提供する。

前提となるハードウェアは Armv8-M の TrustZone(Cortex-M23 / M33 / M35P / M55 / M85)である。 CPU がセキュア状態と非セキュア状態を持ち、メモリと周辺機器を SAU / IDAU(セキュリティ属性ユニット)で 2 つの世界に分ける(iotsec 編 07 章)。 TrustZone のないコア(Cortex-M4 など)でも、デュアルコア(CPU コアが 2 つある構成)で片方をセキュア専用にする構成(PSoC 64、LPC55S69 の一部構成)で動く。

TF-M の構造 — SPE と NSPENSPE(非セキュア)アプリケーション + RTOS読者のコード。psa_* を呼ぶNS インタフェースライブラリlibtfm_api_ns.a: 引数を invec/outvec にベニアのアドレス表(s_veneers.o)TrustZone(SAU / IDAU)SPE(セキュア)ベニア(SG 命令の入口)SPM(Secure Partition Manager)振り分け・メモリ検査・隔離CryptoTF-PSA-CryptoITS / PSストレージAttestationIAK、測定値Platform / FWUリセット、NV カウンタ、更新BL2(MCUboot)が両方のイメージを検証して起動
PSA サービスはセキュア側のパーティションとして動く。非セキュア側はベニアを通してしか入れない
用語意味
SPE(Secure Processing Environment)セキュア側の世界。TF-M 本体と PSA サービスが動く
NSPE(Non-Secure Processing Environment)非セキュア側。RTOS とアプリケーションが動く。読者のコードはここ
SPM(Secure Partition Manager)セキュア側のカーネルに相当。パーティションの起動、呼び出しの振り分け、隔離の実施
セキュアパーティション(Secure Partition)1 つのサービスを収めた「部屋」。Crypto パーティション、ITS パーティションなど。マニフェスト(YAML——人が読み書きしやすい設定ファイル形式——で書く宣言書)で権限・メモリ・提供するサービスを宣言
PSA-RoT / Application RoTパーティションの格。PSA-RoT は TF-M 標準のサービス、ARoT はベンダやアプリケーションが追加するもの。隔離レベルによって扱いが変わる
ベニア(veneer)NSPE から SPE への入口関数。Armv8-M の SG(Secure Gateway)命令で始まる小さなコード。非セキュア側はここにしか飛べない
BL2TF-M のブートローダ。実体は MCUboot。SPE と NSPE のイメージを検証してから起動する

2. 呼び出しの流れ — psa_sign_hash() が壁を越えるまで

非セキュア側のアプリケーションが psa_sign_hash(key, alg, hash, 32, sig, 64, &len) を呼ぶと、次のことが起きる。

  1. NS インタフェースライブラリ(tfm_ns_interface、libtfm_api_ns.a)の psa_sign_hash() が呼ばれる。これは本物ではなく、引数を入出力ベクタ(psa_invec / psa_outvec——「このアドレスからこの長さ」の記述)にまとめ、psa_call() を呼ぶスタブ
  2. psa_call() は、RTOS 用のロック(tfm_ns_interface_dispatch() の中のミューテックス)を取り、ベニア(tfm_psa_call_veneer)に分岐する
  3. ベニアの SG 命令で CPU がセキュア状態に切り替わる。非セキュア側のレジスタとスタックはそのままで、SPM がそれを引き取る
  4. SPM は、渡されたポインタがすべて非セキュア側のメモリを指しているか検査する(セキュア側のメモリを指していたら PSA_ERROR_PROGRAMMER_ERROR——非セキュア側がセキュアメモリを読み書きさせようとする攻撃を防ぐ)
  5. SPM が Crypto パーティションに要求を渡す(隔離レベルによっては別スタック・別 MPU 設定に切り替える)
  6. Crypto パーティションの中で、本物の(Mbed TLS / TF-PSA-Crypto の)psa_sign_hash() が実行される。鍵は Crypto パーティションのメモリか ITS にある
  7. 結果を outvec のバッファ(非セキュア側)に書き、psa_status_t を返してセキュア状態から復帰する

往復のコストは、TF-M の隔離レベル 1 で数 µs、レベル 2〜3 で 10〜20 µs 程度(Cortex-M33 100 MHz)。 署名 1 回(数 ms)に比べれば無視できるが、1 バイトずつ psa_hash_update() を呼ぶような書き方は避ける。

psa_sign_hash() が壁を越えるまでアプリNS ライブラリSPMCrypto パーティションpsa_sign_hash(key, alg, hash, sig, …)invec/outvec に詰め、ロック取得psa_call → ベニア → SG 命令ポインタが NS メモリか検査要求を渡す(隔離レベルに応じ MPU 切替)本物の psa_sign_hash(鍵はここ)outvec に署名を書き、psa_status_t を返す往復 5〜20 µs。検査に失敗すると PSA_ERROR_PROGRAMMER_ERROR(−129)
非セキュア側の関数はスタブ。本物はパーティションの中で走る

2.1 SFN モデルと IPC モデル

パーティションが要求を受け取る方式が 2 つある。

SFN(Secure Function)モデルIPC モデル
仕組みSPM がパーティションの関数を直接呼ぶパーティションが自分のスレッドで psa_wait() して要求を待つ
特徴小さく速い。Profile Small / Medium の既定パーティション間の非同期通信ができる。Profile Large
アプリケーションへの影響なし。どちらも NSPE からは同じ psa_*

2.2 隔離レベル

レベル何が隔離されるか用途
1SPE と NSPE だけ。セキュア側のパーティションは互いに読める小さな製品。Profile Small
2さらに PSA-RoT と ARoT の間を MPU で隔離標準。Profile Medium の既定
3すべてのパーティションを互いに隔離PSA Certified Level 3 を狙う製品

レベルが上がるほど、呼び出しごとの MPU 再設定で遅くなり、RAM も増える。

3. ビルド

TF-M は CMake(ビルド手順を記述する汎用ツール)で構築する。主要オプションだけ示す。

git clone https://git.trustedfirmware.org/TF-M/trusted-firmware-m.git
cd trusted-firmware-m
cmake -S . -B build \
      -DTFM_PLATFORM=arm/mps2/an521 \          # または stm/stm32u5xx など
      -DTFM_TOOLCHAIN_FILE=toolchain_GNUARM.cmake \
      -DTFM_PROFILE=profile_medium \            # small / medium / medium_arot-less / large
      -DTFM_ISOLATION_LEVEL=2 \
      -DTFM_PARTITION_CRYPTO=ON \
      -DTFM_PARTITION_INTERNAL_TRUSTED_STORAGE=ON \
      -DTFM_PARTITION_PROTECTED_STORAGE=ON \
      -DTFM_PARTITION_INITIAL_ATTESTATION=ON \
      -DTFM_PARTITION_PLATFORM=ON \
      -DBL2=ON \                                # MCUboot を使う
      -DMCUBOOT_IMAGE_NUMBER=2 \                # S と NS を別イメージに
      -DCMAKE_BUILD_TYPE=MinSizeRel
cmake --build build -- install

install で build/api_ns/ に NSPE 側が必要なもの一式(ヘッダ psa/*.h、libtfm_api_ns.a、ベニアのアドレスを持つ s_veneers.o、リンカスクリプト(メモリ配置を決めるリンカの設定ファイル)の断片、platform/ の設定)が出力される。 非セキュア側のアプリケーションはこれをリンクするだけで PSA API が呼べる。

オプション意味
TFM_PROFILE機能セットの既定値。small: Crypto(対称のみ)+ ITS、レベル 1。medium: 非対称暗号 + PS + 認証、レベル 2。large: IPC、全機能、レベル 3
TFM_PARTITION_*各サービスの有無。切ればフラッシュが減る
CRYPTO_ASYM_SIGN_MODULE_ENABLED などCrypto パーティション内の機能単位の ON/OFF
TFM_MBEDCRYPTO_CONFIG_PATH / TFM_MBEDCRYPTO_PSA_CRYPTO_CONFIG_PATHMbed TLS の設定ヘッダ(PSA_WANT_*)を差し替える
ITS_MAX_ASSET_SIZE / PS_MAX_ASSET_SIZEストレージの 1 オブジェクトの最大サイズ(13 章)
CRYPTO_ENGINE_BUF_SIZECrypto パーティションのヒープ。RSA を使うなら増やす(既定 0x2000 前後)
TFM_NS_MANAGE_NSID複数の NS クライアント ID を RTOS が管理する
CONFIG_TFM_ENABLE_FPセキュア側で FPU(浮動小数点演算ユニット)を使うか
ビルドの主要オプションTFM_PLATFORMarm/mps2/an521、stm/stm32u5xx などTFM_PROFILEsmall / medium / medium_arot-less / largeTFM_ISOLATION_LEVEL1 / 2 / 3TFM_PARTITION_*CRYPTO / ITS / PS / INITIAL_ATTESTATION / PLATFORM / FIRMWARE_UPDATEBL2 / MCUBOOT_IMAGE_NUMBERブートローダの有無、S/NS を別イメージにITS_MAX_ASSET_SIZE / PS_MAX_ASSET_SIZE1 オブジェクトの最大。RSA 鍵は既定に入らないCRYPTO_ENGINE_BUF_SIZECrypto パーティションのヒープ。RSA なら増やすTFM_MBEDCRYPTO_PSA_CRYPTO_CONFIG_PATHPSA_WANT_* のヘッダを差し替えcmake --build build -- install で build/api_ns/ に NSPE 用一式(ヘッダ・ライブラリ・ベニア表)が出る
プロファイルで機能セットを選び、パーティションとサイズ上限を調整する

4. 非セキュア側の書き方

4.1 初期化

RTOS を使うなら、NS インタフェースにロックを与える必要がある(複数スレッドが同時にセキュア側を呼ぶのを直列化する)。

#include "tfm_ns_interface.h"

/* RTOS 用: セキュア呼び出しの直列化 */
static SemaphoreHandle_t s_lock;
int32_t tfm_ns_interface_dispatch(veneer_fn fn, uint32_t a0, uint32_t a1, uint32_t a2, uint32_t a3)
{
    xSemaphoreTake(s_lock, portMAX_DELAY);
    int32_t r = fn(a0, a1, a2, a3);
    xSemaphoreGive(s_lock);
    return r;
}
uint32_t tfm_ns_interface_init(void) { s_lock = xSemaphoreCreateMutex(); return s_lock ? 0 : 1; }

TF-M には FreeRTOS / RTX / Zephyr 向けのこの実装が同梱されている(interface/src/os_wrapper/)。

その後は、通常どおり psa_crypto_init() を呼ぶ。TF-M では実体は「セキュア側がすでに初期化済み」の確認である。

4.2 メモリの規則

セキュア側に渡すバッファは、すべて非セキュア側の RAM になければならない。

PSA_ERROR_PROGRAMMER_ERROR(−129)を見たら、まずポインタの指す先を疑う。

4.3 クライアント ID と鍵の所有

TF-M は、呼び出し元ごとにクライアント ID(int32_t)を持つ。セキュアパーティションは正、非セキュア側は負(既定 −1)である。 PSA 鍵は「作ったクライアント」の所有になり、他のクライアントは同じ鍵 ID を指定しても使えない(PSA_ERROR_INVALID_HANDLE)。 ITS の UID も同様に名前空間が分かれる(13 章)。

非セキュア側の RTOS が複数のスレッドやプロセスを別クライアントとして扱う(TFM_NS_MANAGE_NSID)と、スレッド A が作った鍵はスレッド B から見えなくなる。 既定では非セキュア側全体が 1 つのクライアント(−1)なので、通常は気にしなくてよい。

クライアント ID と鍵の所有NSPE(クライアント ID = −1)既定では非セキュア側全体が 1 つCrypto パーティション(正の ID)Attestation パーティション(正の ID)鍵 ID と ITS の UID は「作ったクライアント」の名前空間に属する他のクライアントが同じ ID を指定しても INVALID_HANDLE。TFM_NS_MANAGE_NSID で NS 側のスレッドを別クライアントにすると、互いの鍵が見えなくなる
同じ番号でも所有者が違えば別の鍵

5. セキュア側に自分のコードを置く

「鍵を使う処理そのもの」を非セキュア側から見えなくしたい——例えば「乗っ取られても暗号化ログの復号鍵は使わせない」——なら、自分のセキュアパーティションを作る。

  1. partitions/my_service/ にマニフェスト my_service.yaml(名前、パーティション ID、提供するサービス名と SID——サービスを識別する 32 ビット番号——、必要な権限、スタックサイズ)と実装 my_service.c を書く
  2. サービスの入口関数で psa_invec から引数を受け取り、PSA Crypto API(パーティション内から直接呼べる)で処理し、psa_outvec に書く
  3. NSPE 側の呼び出しスタブ(psa_call(handle, type, in_vec, in_len, out_vec, out_len) を包む関数)を書く
  4. tfm_manifest_list.yaml にパーティションを登録し、ビルドする

これは FF-M(Firmware Framework for M)の仕様に従う作業で、TF-M のドキュメント "Adding Secure Partition" が手順を示している。 Crypto / ITS などの標準サービスを使うだけなら不要である。

6. デバッグ

7. 手を動かす

壁越しの呼び出しを追う

8. 仕様書に逃がす

項目参照先
FF-M(Firmware Framework for M)仕様: psa_connect / psa_call / psa_close、マニフェスト形式、SIDArm "PSA Firmware Framework for M" 1.1
TF-M のプラットフォームポート(新しいチップに移植する)TF-M docs "Platform integration guide"
MCUboot の署名鍵の差し替え、イメージの署名手順TF-M docs "Secure boot"、bl2/ext/mcuboot/
プロビジョニング(HUK、IAK、ライフサイクルの書き込み)TF-M docs "Provisioning"、platform/ext/common/provisioning_bundle
Firmware Update パーティション(psa_fwu_*)TF-M docs "Firmware Update Service"、PSA Firmware Update API 1.0

この章のポイント