Chapter 13
セキュアストレージ — ITS と PS で小さな秘密を保存する
この章のゴール.
psa_its_set/get/get_info/removeの 4 関数と、Protected Storage との違いを理解し、 何を ITS に、何を PS に、何を PSA 鍵にすべきかを決められるようになること。WRITE_ONCEフラグとリプレイ保護の意味を説明できること。この章で使う既出の用語(定義は各リンク先). PSA(01 章 1 節)、ビット(02 章 4 節)、type(03 章 11 節)、場所(03 章 7 節)、作成(04 章 1 節)、MAC(06 章 1 節)、AES(07 章 1 節)、カウンタ(08 章 4 節)、ノンス(08 章 1 節)、平文(08 章 1 節)、HKDF(10 章)、info(10 章 3 節)、パスワード(10 章 2 節)、容量(10 章 5 節)
1. 2 つのストレージ API
PSA Secure Storage API は、小さなデータを「UID」という番号で保存する、単純な鍵値ストアである。 ファイルシステムではない——ディレクトリもファイル名もなく、64 ビットの整数で 1 つのデータ塊を指す。
| ITS(Internal Trusted Storage、内部信頼ストレージ) | PS(Protected Storage、保護ストレージ) | |
|---|---|---|
| ヘッダ | psa/internal_trusted_storage.h | psa/protected_storage.h |
| 関数の接頭辞 | psa_its_ | psa_ps_ |
| 置き場所 | チップ内部のフラッシュ(セキュア側) | 外部フラッシュでもよい |
| 保護の仕組み | 「内部にあるから読めない」(フラッシュ読み出し保護+TrustZone) | 暗号化+改ざん検出+リプレイ防止をソフトウェアで行う(AEAD + ITS に保存したカウンタ) |
| 容量 | 小さい(数 KB〜数十 KB) | 大きい(外部フラッシュのサイズ次第) |
| 速さ | 速い | 遅い(暗号化と MAC の計算) |
| 誰が使うか | PSA Crypto の永続鍵の保存先。アテステーションのカウンタ。ブートの状態 | アプリケーションの設定、証明書、ログ、資格情報 |
| 呼べる側 | セキュア側の内部サービス。TF-M では非セキュア側からも呼べる設定が既定 | アプリケーション(非セキュア側) |
ITS の "Trusted" は「信頼できる場所にある」であって「暗号化されている」ではない。 ITS 自体は平文で書く(TF-M の既定)。 その代わり ITS のフラッシュ領域はセキュア側だけがアクセスでき、チップ外から読めない前提で使う。
PS はその前提が成り立たない場所(外部 SPI フラッシュなど)のためのもので、書く前に AEAD で暗号化し、ITS に保存した鍵とカウンタで改ざんとロールバック(古いデータを書き戻す攻撃)を検出する。
2. ITS の 4 関数
typedef uint64_t psa_storage_uid_t;
typedef uint32_t psa_storage_create_flags_t;
struct psa_storage_info_t {
size_t capacity; /* 確保された容量 */
size_t size; /* 実際のデータ長 */
psa_storage_create_flags_t flags; /* 作成時のフラグ */
};
psa_status_t psa_its_set(psa_storage_uid_t uid, size_t data_length, const void *p_data,
psa_storage_create_flags_t create_flags);
psa_status_t psa_its_get(psa_storage_uid_t uid, size_t data_offset, size_t data_size,
void *p_data, size_t *p_data_length);
psa_status_t psa_its_get_info(psa_storage_uid_t uid, struct psa_storage_info_t *p_info);
psa_status_t psa_its_remove(psa_storage_uid_t uid);| 関数 | 何をするか | 注意 |
|---|---|---|
psa_its_set | UID にデータを書く。あれば上書き、なければ作る | 部分書き込みはない。常にデータ全体を渡す。WRITE_ONCE のデータに再度 set すると NOT_PERMITTED |
psa_its_get | UID からデータを読む。data_offset からの部分読み出しができる | 出力の 3 つ組(buf, size, &len)。data_size が実データより大きければ実データ長だけ返る(BUFFER_TOO_SMALL ではない) |
psa_its_get_info | 長さとフラグを調べる | 「存在するか」の確認にも使う(なければ DOES_NOT_EXIST) |
psa_its_remove | 消す | WRITE_ONCE のデータは消せない(NOT_PERMITTED) |
#define UID_BOOT_COUNT ((psa_storage_uid_t)0x0000000100000001ULL)
psa_status_t bump_boot_count(uint32_t *count)
{
size_t len;
psa_status_t st = psa_its_get(UID_BOOT_COUNT, 0, sizeof *count, count, &len);
if (st == PSA_ERROR_DOES_NOT_EXIST) { *count = 0; st = PSA_SUCCESS; }
if (st != PSA_SUCCESS || (len != 0 && len != sizeof *count)) return st ? st : PSA_ERROR_DATA_CORRUPT;
(*count)++;
return psa_its_set(UID_BOOT_COUNT, sizeof *count, count, PSA_STORAGE_FLAG_NONE);
}UID は 64 ビットで、0 は無効である。 鍵 ID と同じく、プロジェクトのヘッダで台帳を作る。 TF-M では、ITS の UID は呼び出したクライアント(パーティション)ごとに名前空間が分かれる——非セキュア側の UID 5 とセキュア側の Crypto パーティションの UID 5 は別物である。だから PSA 永続鍵のストレージと衝突する心配はない。
3. 作成フラグ
psa_its_set() の最後の引数は、最初に作るときだけ意味を持つフラグである(上書き時は無視される)。
| フラグ | 値 | 意味 |
|---|---|---|
PSA_STORAGE_FLAG_NONE | 0 | 通常。上書き・削除できる |
PSA_STORAGE_FLAG_WRITE_ONCE | 1 | 一度書いたら二度と変えられない・消せない。工場出荷時のデータ(装置 ID、ルート公開鍵)向け。テスト中に使うと消せなくなるので注意 |
PSA_STORAGE_FLAG_NO_CONFIDENTIALITY | 2 | 秘匿性は不要(改ざん検出だけ)。PS で暗号化を省いて速くする |
PSA_STORAGE_FLAG_NO_REPLAY_PROTECTION | 4 | リプレイ防止は不要。PS でカウンタ更新(ITS 書き込み)を省いて速くする |
後の 2 つは PS で意味を持つ。ITS はフラグを記録するだけで動作は変わらない(実装による)。
4. PS の関数
PS は ITS と同じ 4 関数(psa_ps_set/get/get_info/remove)に加え、次の拡張がある。
psa_status_t psa_ps_create(psa_storage_uid_t uid, size_t capacity, psa_storage_create_flags_t create_flags);
psa_status_t psa_ps_set_extended(psa_storage_uid_t uid, size_t data_offset, size_t data_length, const void *p_data);
uint32_t psa_ps_get_support(void);psa_ps_create()+psa_ps_set_extended(): 容量を先に確保して、部分的に書く。大きなデータ(証明書チェーン、ログ)を少しずつ書くため。psa_ps_get_support()がPSA_STORAGE_SUPPORT_SET_EXTENDEDを返す実装だけ対応- 通常は
psa_ps_set()/psa_ps_get()だけで足りる
PS の内部動作(TF-M の場合)を知っておくと、性能と安全性の限界が分かる。
- データを AEAD(AES-GCM)で暗号化する。鍵はハードウェア固有鍵(HUK、Hardware Unique Key。チップごとに違う焼き込み済みの秘密)から HKDF で導出
- 各オブジェクトにバージョン番号を付け、それを AD に含める
- オブジェクト表(どの UID がどこにあるか)を ITS に保存する。これがリプレイ防止の根拠——外部フラッシュを丸ごと古い状態に戻しても、ITS 側の表と食い違う
- フラッシュへの書き込みはセクタ単位なので、小さなデータの更新でも数 ms〜数十 ms かかる
5. 何をどこに置くか
| データ | 置き場所 | 理由 |
|---|---|---|
| 秘密鍵、対称鍵 | PSA 永続鍵(12 章) | ストレージに自分で書かない。用途制限と非エクスポートが効く |
| 相手の公開鍵、ルート証明書 | PSA 永続鍵(公開鍵型)または ITS に WRITE_ONCE | 差し替えられないことが重要 |
| 装置 ID、シリアル番号 | ITS WRITE_ONCE | 小さく、変わらない |
| 起動回数、AEAD ノンスのカウンタ、ロールバック番号 | ITS | 小さく、頻繁に更新。改ざんされたくない |
| Wi-Fi のパスワード、クラウドの接続情報 | PS | 秘密だがサイズが大きめ。アプリケーションが読み書きする |
| 装置証明書(自分の) | PS または ITS | 秘密ではないが改ざんは困る。サイズは 500〜1500 バイト |
| 設定値(ユーザー設定) | PS | 秘密性は低いが改ざん検出は欲しい |
| ログ、計測データ | 通常のファイルシステム | セキュアストレージは遅く小さい。必要なら AEAD で自分で暗号化 |
原則: 鍵は鍵 API に、小さな不変データは ITS に、アプリケーションの秘密は PS に、大きなデータはセキュアストレージの外に。
「パスワードを ITS に入れればよいのでは」.
ITS は非セキュア側からも呼べる(TF-M の既定)ので、非セキュア側のコードが乗っ取られれば
psa_its_get()で読める。 セキュアストレージは「物理的な読み出し」と「他のパーティションからのアクセス」を防ぐが、書いた本人からは読める。 だから「アプリケーションが読める必要のない秘密」は PSA 鍵にして、値を取り出せない形で持つのが正しい。 Wi-Fi パスワードのように、アプリケーションが平文で必要な秘密だけを PS に置く。
6. ITS の実装と設定(Mbed TLS / TF-M)
| 環境 | ITS の実体 | 設定 |
|---|---|---|
| Mbed TLS(PC) | MBEDTLS_PSA_ITS_FILE_C: カレントディレクトリのファイル | MBEDTLS_PSA_CRYPTO_STORAGE_C と組で有効化 |
| Mbed TLS(マイコン、単体) | 自分で psa_its_* を実装する(内蔵フラッシュに書く。ウェアレベリング——書き込み回数を均して寿命を延ばす処理——は自前)。または Zephyr(オープンソースの RTOS、リアルタイム OS)などのセキュアストレージ実装 | MBEDTLS_PSA_CRYPTO_STORAGE_C を有効にし、psa_its_* をリンクする |
| TF-M | ITS パーティション(TFM_PARTITION_INTERNAL_TRUSTED_STORAGE)。フラッシュ領域は ITS_FLASH_AREA_ADDR / ITS_FLASH_AREA_SIZE で指定 | TFM_PARTITION_PROTECTED_STORAGE で PS を追加。ITS_MAX_ASSET_SIZE(既定 512 バイト前後)を超えるデータは書けない |
TF-M の ITS_MAX_ASSET_SIZE は初心者が必ず踏む制限である。 2048 ビットの RSA 鍵ペア(約 1200 バイト)を永続鍵にしようとして PSA_ERROR_INSUFFICIENT_STORAGE や INVALID_ARGUMENT が出たら、これを疑う。 ビルド時に -DITS_MAX_ASSET_SIZE=2048 のように広げる(フラッシュ領域も増える)。
7. 手を動かす
ITS を操作する
8. 仕様書に逃がす
| 関数・定数 | 用途 |
|---|---|
psa_ps_create() / psa_ps_set_extended() / psa_ps_get_support() | 部分書き込み(対応実装のみ) |
PSA_STORAGE_SUPPORT_SET_EXTENDED | 上の対応を示すビット |
PSA_ITS_API_VERSION_MAJOR/MINOR、PSA_PS_API_VERSION_* | API バージョン |
PSA_ERROR_DATA_CORRUPT / PSA_ERROR_DATA_INVALID | 保存データの破損 / 形式不正。バックアップからの復旧やリセットの判断に使う |
この章のポイント
- ストレージ API は UID(64 ビット)で引く小さな鍵値ストア。
set / get / get_info / removeの 4 関数 - ITS は「内部にあるから信頼」(平文、速い、小さい)。PS は「暗号化+改ざん検出+リプレイ防止」(外部フラッシュ可、遅い)
- 鍵は鍵 APIに。ITS/PS には鍵の値を自分で書かない
WRITE_ONCEは工場データ用。テストで使うと消せない- TF-M では
ITS_MAX_ASSET_SIZEに注意。RSA 鍵ペアは既定値に入らない