Chapter 18
落とし穴とチェックリスト — 出荷前に確かめること
この章のゴール.
PSA を使ったコードで実際に起きる失敗を、エラーコード別・設計別に整理し、 レビューで確認する項目を持つこと。 そして本シリーズで扱わなかった API を、名前と参照先だけ把握すること。
この章で使う既出の用語(定義は各リンク先). PSA(01 章 1 節)、認証制度(01 章 1 節)、サイズマクロ(02 章 4 節)、操作オブジェクト(02 章 5 節)、algorithm(03 章 11 節)、type(03 章 11 節)、usage(03 章 11 節)、場所(03 章 7 節)、DER(04 章 2 節)、generate(04 章 10 節)、import(04 章 10 節)、破棄(04 章 1 節)、error(05 章 4 節)、ハッシュ(05 章 1 節)、乱数(05 章 1 節)、HMAC-SHA256(06 章 1 節)、AES(07 章 1 節)、CTR(07 章 9 節)、対称暗号(07 章 1 節)、カウンタ(08 章 4 節)、ノンス(08 章 1 節)、平文(08 章 1 節)、チャレンジ(09 章 5 節)、HKDF(10 章)、info(10 章 3 節)、パスワード(10 章 2 節)、ECDH(11 章 1 節)、共有秘密(11 章 1 節)、ITS(13 章 1 節)、IAK(14 章 4 節)、Profile(14 章 3 節)、トークン(14 章)、ドライバ(15 章)、ベニア(16 章 1 節)
1. エラーコード別の原因表
デバッグ中に最初に見るのはエラーコードである。コード → 原因の逆引きを、遭遇頻度の順に並べる。
| エラー | 原因(頻度順) |
|---|---|
NOT_PERMITTED (−133) | ① 鍵の usage に必要なフラグがない(署名に SIGN_HASH、MAC に SIGN_MESSAGE、導出に DERIVE、export_key に EXPORT) ② 鍵の algorithm と操作のアルゴリズムが不一致(ECDH vs KEY_AGREEMENT(ECDH, HKDF)、GCM vs CTR、ECDSA(SHA_256) vs ECDSA(SHA_384)) ③ WRITE_ONCE のストレージに再書き込み ④ read-only 鍵の破棄 ⑤ TF-M で他クライアントの鍵 |
INVALID_ARGUMENT (−135) | ① import するバイト列の形式・長さが type と合わない(PEM を渡した、DER の包装を外していない、公開鍵の 0x04 がない) ② sign_hash のハッシュ長がアルゴリズムと不一致 ③ 鍵長が不正(Curve25519 に 256、P-521 に 512) ④ 揮発鍵に ID を設定、永続鍵に ID 未設定 ⑤ AEAD のノンス長が規定外 ⑥ psa_hash_compare に長さ違いの期待値 ⑦ CBC_NO_PADDING に 16 の倍数でない入力 |
NOT_SUPPORTED (−134) | ① PSA_WANT_* の宣言漏れ(特に KEY_PAIR_GENERATE / _EXPORT) ② 曲線の宣言漏れ(PSA_WANT_ECC_SECP_R1_256) ③ TF-M のプロファイルで非対称暗号が無効(Profile Small) ④ ドライバがそのアルゴリズムを持たず、ソフトウェア実装も無効 |
BAD_STATE (−137) | ① psa_crypto_init() 忘れ ② 分割操作の順序違反(set_iv の前に update、update_ad を update の後、sign_setup に verify_finish) ③ エラー後に abort せず再利用 ④ 未初期化の操作オブジェクト |
BUFFER_TOO_SMALL (−138) | ① サイズマクロを使わず決め打ち ② cipher_encrypt の出力に IV 分(16)を足していない ③ aead_encrypt の出力にタグ分(16)を足していない ④ RSA 鍵のエクスポートで DER のサイズを見誤り |
INVALID_HANDLE (−136) | ① 未初期化の psa_key_id_t(0) ② 破棄済みの鍵 ③ 永続鍵の ID を間違えた ④ TF-M で別クライアントの鍵 ⑤ 実装によっては存在しない永続鍵(DOES_NOT_EXIST の代わりに返る) |
INVALID_SIGNATURE (−149) | ① 本当に改ざん・偽造 ② 鍵・アルゴリズム・データのどれかが送信側と違う ③ AEAD の AD やノンスが違う ④ ECDSA 署名の形式(DER を raw として渡した) ⑤ ハッシュを取った範囲が違う |
ALREADY_EXISTS (−139) | 永続鍵の ID がすでに使われている(前回の実行の残り)。「あれば使う」に直す |
INSUFFICIENT_MEMORY (−141) | ① 鍵スロット上限(MBEDTLS_PSA_KEY_SLOT_COUNT) ② TF-M の CRYPTO_ENGINE_BUF_SIZE ③ 揮発鍵の破棄漏れ(リーク——使い終わっても解放されず溜まっていくこと) |
INSUFFICIENT_STORAGE (−142) | ITS/PS の領域が満杯、または ITS_MAX_ASSET_SIZE 超え |
INSUFFICIENT_ENTROPY (−148) | エントロピー源が未接続(15 章 3 節) |
PROGRAMMER_ERROR (−129) | TF-M でセキュア側のメモリやフラッシュ定数を指すポインタを渡した |
COMMUNICATION_FAILURE (−145) | セキュアエレメントの I²C/SPI 応答なし。TF-M のベニア呼び出しが割り込み中に行われた |
2. 設計の落とし穴
エラーコードにならない——動くが安全でない——失敗のほうが危険である。
| 落とし穴 | なぜ危険か | 正しい形 |
|---|---|---|
rand() や時刻をノンス・鍵・チャレンジに使う | 予測できる。ECDSA なら秘密鍵が漏れる | psa_generate_random() / psa_generate_key() |
| AEAD のノンスがカウンタで、再起動で 0 に戻る | 同じ鍵・同じノンスで GCM の認証鍵が漏れる | 起動世代を含める、またはカウンタを ITS に周期保存 |
対称暗号(psa_cipher_*)だけで「暗号化」 | 改ざん検出なし。復号は失敗しない | AEAD |
MAC の検証を psa_mac_compute + memcmp で | タイミング攻撃 | psa_mac_verify |
| 署名検証の公開鍵が書き換え可能な場所にある | 公開鍵を差し替えれば任意のイメージが通る | WRITE_ONCE ITS、read-only 鍵、ブートローダ埋め込み |
| 検証用公開鍵をサーバから受け取って使う | 信頼の起点が「そのサーバとの接続」に依存する | 工場で書き込む |
| 1 つの鍵を複数用途で使う(署名と ECDH、暗号化と MAC) | 用途混同攻撃 | 鍵ごとに用途 1 つ。KDF で分ける |
| ECDH の共有秘密をそのまま AES 鍵に | 偏りがある。毎回同じ | KDF を通す |
| KDF の info を省略 | 送信鍵 = 受信鍵(反射攻撃) | 方向・用途を info に |
EXPORT を「とりあえず」付ける | 乗っ取られたコードが鍵を読める | 理由を説明できる鍵だけに |
| 戻り値を無視して次へ進む | 暗号化したつもりで平文送信、検証したつもりで未検証 | PSA_CHECK と goto cleanup |
AEAD 分割復号で verify 前の出力を使う | 改ざんデータを処理する | verify 成功まで保留、または失敗時に無効化 |
| OTA でバージョン比較をしない | 署名の正しい古い脆弱イメージに戻せる | 単調増加のバージョンを ITS に |
| PC の Mbed TLS(ファイル ITS)の感覚でマイコンに載せる | 永続鍵が平文でフラッシュにある | TF-M、フラッシュ読み出し保護、セキュアエレメント |
| テスト用 IAK / MCUboot 鍵のまま出荷 | 誰でも正当なトークン・イメージを作れる | プロビジョニングで差し替え |
| 割り込みハンドラから PSA を呼ぶ | ミューテックス・フラッシュ書き込みで破綻 | タスクに委譲 |
操作オブジェクトを memcpy | ハードウェア状態が複製されない | psa_hash_clone のみ可 |
3. パフォーマンスの目安
「遅い」と感じたときの当たりをつける数字である(Cortex-M33 100 MHz、ソフトウェア実装、-Os。ハードウェアアクセラレータで AES/SHA は 10 倍以上速くなる)。
| 操作 | 時間の目安 |
|---|---|
| SHA-256(1 KB) | 0.1〜0.2 ms |
| AES-128-GCM(1 KB) | 0.3〜0.6 ms |
| HMAC-SHA256(64 B) | 20〜40 µs |
| ECDSA P-256 署名 | 20〜60 ms |
| ECDSA P-256 検証 | 40〜100 ms |
| ECDH P-256 | 20〜60 ms |
| P-256 鍵生成 | 20〜60 ms |
| RSA-2048 署名 | 300〜1000 ms |
| RSA-2048 検証 | 10〜30 ms |
| RSA-2048 鍵生成 | 5〜60 秒(ばらつく) |
| TF-M 越しの呼び出し 1 回のオーバーヘッド | 5〜20 µs |
| ITS 書き込み(小) | 1〜10 ms(フラッシュ消去を伴うと数十 ms) |
ECC の計算は MBEDTLS_ECP_WINDOW_SIZE / MBEDTLS_ECP_FIXED_POINT_OPTIM で 2 倍程度、ハードウェア ECC アクセラレータで 10 倍程度変わる。 割り込みを長時間止める処理の中で署名を呼ばない。中断可能な署名 API(09 章)はこのためにある。
4. 本シリーズで扱わなかった API
「そういうものがある」と知っておくだけで、必要になったときに仕様書を引ける。
| API / 関数群 | 何か | 参照先 |
|---|---|---|
PAKE(psa_pake_*) | パスワード認証鍵交換。SPAKE2+(Matter)、EC-JPAKE(Thread) | Crypto API 1.1 拡張 "PAKE" |
中断可能な署名(psa_sign_hash_start/complete 等) | 長い署名を分割実行 | Crypto API 1.1 "Interruptible operations" |
鍵の包み込み(psa_wrap_key / psa_unwrap_key) | AES-KW で鍵を包む | Crypto API 1.3 |
暗号化された鍵の import(psa_import_key の拡張、psa_generate_key_custom) | 生成パラメータ | Crypto API 1.2 |
| SP 800-108 KDF、XChaCha20-Poly1305、SHAKE | 追加アルゴリズム | Crypto API 1.2 / 1.3 |
Firmware Update API(psa_fwu_start / write / finish / install / accept / reject / query / clean) | 更新イメージの受け取りとスロット切替を標準化。TF-M の FWU パーティションが MCUboot と連携 | PSA Firmware Update API 1.0 |
Platform サービス(tfm_platform_system_reset、tfm_platform_ioctl、NV カウンタ) | TF-M 固有。リセット、NV カウンタ(ロールバック番号)の読み書き | TF-M docs "Platform Service" |
FF-M クライアント API(psa_framework_version、psa_version、psa_connect、psa_call、psa_close) | 自作パーティションとの通信 | FF-M 1.1 |
| PSA Certified Crypto API for ML / PQC 拡張 | ML-KEM / ML-DSA(ポスト量子暗号)を扱う PSA_ALG_ML_DSA / PSA_KEY_TYPE_ML_KEM など | Crypto API 1.3 以降の拡張(2025 年時点で仕様策定中〜初期実装) |
Secure Element ドライバの旧インタフェース(psa_drv_se_*) | 3.x の古い SE ドライバ API。非推奨 | Mbed TLS docs |
5. 出荷前チェックリスト
レビュー用チェックリスト
6. 学び続けるために
- 仕様書: arm-software.github.io/psa-api — Crypto / Storage / Attestation / Firmware Update の HTML 版。関数の引数を確認する辞書として
- Mbed TLS / TF-PSA-Crypto: github.com/Mbed-TLS。
programs/psa/にサンプル(psa_constant_namesでエラーコードや定数を名前に戻せる)。docs/psa-transition.mdが旧 API からの対応表 - TF-M: trustedfirmware-m.readthedocs.io。
tf-m-testsの回帰テストが「正しい呼び方」のコード例として最良 - PSA Certified: psacertified.org — 認証制度、API 認証のテストスイート(
psa-arch-tests。自分の実装が仕様どおりか確かめられる) - 本サイトの関連編: IoT セキュリティ編(Root of Trust、セキュアブート、TrustZone、認証制度)、暗号編(アルゴリズムの中身)、Matter 編(PSA を使う実際のスタック)
この章のポイント
- エラーコードは逆引き表で当たりをつける。
NOT_PERMITTEDは属性、INVALID_ARGUMENTは形式、NOT_SUPPORTEDは設定、BAD_STATEは順序 - 危険なのは動くが安全でない失敗: 乱数、ノンスの繰り返し、認証なし暗号、公開鍵の置き場所、用途混同
- 性能は ECC 数十 ms、RSA 数百 ms〜秒、TF-M 越しは +10 µs
- 扱わなかった API(PAKE、中断可能署名、FWU、FF-M)は名前と参照先だけ持っておく
これで全 18 章は終わりである。シリーズの目次に戻る。