PSA APIs 14 · アテステーション — 「このデバイスは何者か」を署名付きで証明する

Chapter 14

アテステーション — 「このデバイスは何者か」を署名付きで証明する

この章のゴール.

psa_initial_attest_get_token() を呼んでトークンを取得し、 その中身(CBOR の主張の列と COSE 署名)を読めるようになり、 サーバ側で何を検証すべきか、そして「なぜチャレンジ応答より優れているか」を説明できるようになること。

この章で使う既出の用語(定義は各リンク先). PSA(01 章 1 節)、参照実装(01 章 1 節)、type(03 章 11 節)、ハッシュ(05 章 1 節)、乱数(05 章 1 節)、MAC(06 章 1 節)、チャレンジ(09 章 5 節)、認証(09 章 1 節)

1. アテステーションとは何か

アテステーション(attestation、証明)は、 装置が「私はこのハードウェアで、このファームウェアを、この状態で動かしている」と、 改ざんできない形でサーバに申告する仕組みである。

09 章のチャレンジ応答は「秘密鍵を持っている」ことしか証明しない。 乗っ取られた装置も、正規のファームウェアが消されて別のコードが動いている装置も、秘密鍵さえ残っていれば認証を通ってしまう。 アテステーションは、署名の対象に「ブート時に測定したファームウェアのハッシュ」や「ライフサイクル状態」を含めることでこれを防ぐ。

チャレンジ応答とアテステーションの違いチャレンジ応答(9 章)署名対象 = チャレンジ。証明できるのは「秘密鍵を持っている」ことだけ。乗っ取られた装置も通るアテステーション署名対象 = チャレンジ + ブート時に測ったファームウェアのハッシュ + ライフサイクル状態 + 装置 ID署名するのはアプリケーションではなく、セキュア側(TF-M のアテステーションパーティション)アプリが乗っ取られても、セキュア側が独立に測定した値を書き換えられない。だから申告に価値がある
「鍵を持っているか」に加えて「何を動かしているか」を、独立した側が証明する

PSA Initial Attestation API は、この申告書(トークン)を作る 2 つの関数だけからなる。

psa_status_t psa_initial_attest_get_token(const uint8_t *auth_challenge, size_t challenge_size,
                                          uint8_t *token_buf, size_t token_buf_size,
                                          size_t *token_size);
psa_status_t psa_initial_attest_get_token_size(size_t challenge_size, size_t *token_size);

2. 呼び方

uint8_t challenge[32];         /* サーバから受け取った乱数(32 / 48 / 64 バイトのいずれか) */
uint8_t token[1024];
size_t  token_len;

/* 必要なサイズを先に聞くこともできる */
PSA_CHECK(psa_initial_attest_get_token_size(sizeof challenge, &token_len));
/* token_len ≤ sizeof token を確認 */

PSA_CHECK(psa_initial_attest_get_token(challenge, sizeof challenge,
                                       token, sizeof token, &token_len));
send_to_server(token, token_len);

このため、アテステーションはTF-M(または同等のセキュア側実装)がある環境でしか意味を持たない。 Mbed TLS 単体にはこの API はない。 セキュア側が「非セキュア側が何を動かしているか」を独立に測定しているからこそ、申告に価値がある。

トークンの取得と検証の流れ検証サービスアプリ(NSPE)TF-M(SPE)チャレンジ(32 / 48 / 64 バイトの乱数)psa_initial_attest_get_token(challenge)測定値 + 主張を CBOR に → IAK で署名トークン(300〜700 バイト)トークンを転送アプリは鍵にも測定値にも触らない。1 関数を呼ぶだけ
チャレンジを受けてトークンを返す。中身を作るのはセキュア側

3. トークンの中身

トークンは COSE_Sign1(CBOR Object Signing and Encryption の署名 1 つ版、RFC 9052)で包まれた CBOR(Concise Binary Object Representation、JSON をバイナリにしたような形式、RFC 8949)のマップである。 中身の主張(claim、クレーム)は EAT(Entity Attestation Token、RFC 9711)の PSA プロファイルに従う。

主張(claim)CBOR のキー型意味
Nonce10bstrサーバが送ったチャレンジそのもの
UEID(Universal Entity ID)256bstr装置の一意 ID(型バイト + 32 バイト程度)。通常、アテステーション公開鍵のハッシュから作る
Profile265tstr"http://arm.com/psa/2.0.0" など。トークンの形式のバージョン
Client ID2394intトークンを要求した呼び出し元の ID(非セキュア側なら負の値)
Security lifecycle2395uint装置のライフサイクル状態。0x1000 = ASSEMBLY_AND_TEST、0x2000 = PSA_ROT_PROVISIONING、0x3000 = SECURED、0x4000 = NON_PSA_ROT_DEBUG、0x6000 = DECOMMISSIONED など
Implementation ID2396bstrチップ・PSA 実装の識別子(32 バイト)。「どのベンダのどのチップか」
Boot seed2397bstr起動ごとに変わる乱数(32 バイト)。同一起動内のトークンを結びつける
Certification reference2398tstrPSA Certified の認証番号(任意)
Software components2399arrayブート時に測定したソフトウェアの一覧(次節)
Verification service2400tstr検証サービスの URL(任意)
Instance ID / Hardware version256 系 / 2402—旧プロファイルとの互換や追加情報

旧プロファイル(PSA_IOT_PROFILE_1)では、キーが −75000〜−75010 の負の整数だった(arm_psa_nonce = −75008 など)。 古い TF-M や検証ツールのログに負のキーが出たら、こちらである。

トークンの構造 — COSE_Sign1 で包んだ CBORCOSE_Sign1(RFC 9052)protected headeralg: ES256unprotected headerkid(任意)signatureECDSA P-256、64 Bpayload = CBOR マップ(主張、EAT の PSA プロファイル)10 nonce ← チャレンジそのもの256 ueid ← 装置の一意 ID265 profile "http://arm.com/psa/2.0.0"2394 client id 呼び出し元(NSPE は負)2395 security lifecycle 0x3000 = SECURED2396 implementation id チップ・実装の識別子2397 boot seed 起動ごとの乱数2399 sw components [ {type, measurement, version, signer id}, … ]2400 verification service(任意)旧プロファイル(PSA_IOT_PROFILE_1)ではキーが −75000 台の負の整数
外側が署名の包み、中がキー番号付きの主張。核心は 2399 のソフトウェア構成要素

3.1 ソフトウェア構成要素

トークンの核心は Software components(キー 2399)である。 ブートローダ(MCUboot——Arm 系マイコンで広く使われるオープンソースのブートローダ)が起動時に各イメージのハッシュを測り、セキュア側に渡した値がここに入る。

項目キー内容
Measurement type1"BL2", "SPE", "NSPE" など、何を測ったか
Measurement value2イメージのハッシュ(SHA-256、32 バイト)
Version4"1.2.0" などのバージョン文字列
Signer ID5そのイメージに署名した鍵のハッシュ
Measurement description6"SHA256" など、ハッシュのアルゴリズム

サーバはこの配列を見て、「この装置は既知の正しいファームウェア(ハッシュが一致)を動かしているか」を判断できる。 脆弱性のある古いバージョンを動かしている装置には、更新を強制したりアクセスを拒否したりできる。

4. 署名鍵 — IAK

トークンの署名には、IAK(Initial Attestation Key、初期アテステーション鍵)という装置固有の ECDSA P-256 鍵を使う。

TF-M の開発用ビルドには公開されたテスト用 IAKが入っている。 製品では必ず差し替える(attest_key.c またはプロビジョニングバンドル)。 差し替え忘れは「誰でも正当なトークンを作れる装置」を出荷することを意味する。

5. サーバ側の検証

検証サービスが行うことを列挙する。装置側は 1 関数だが、価値は検証側で生まれる。

  1. COSE_Sign1 の構造を解析し、CBOR の主張を取り出す
  2. UEID から登録済みの IAK 公開鍵を引く
  3. 署名を検証する(ECDSA P-256 + SHA-256。署名対象は COSE の Sig_structure)
  4. Nonce が自分の送ったチャレンジと一致することを確認する(再送防止)
  5. Security lifecycle が SECURED であることを確認する(デバッグ状態の装置を拒否)
  6. Software components の各ハッシュが許可リストにあることを確認する
  7. 結果(合格/不合格、その理由)をアプリケーションサーバに返す

Arm は参照実装として Veraison プロジェクト(オープンソースの検証サービス)を提供している。 開発中は、TF-M の iat-verifier(Python ツール)でトークンをデコードして中身を眺めるのが手軽である。

pip install iat-verifier    # TF-M の tools/iat-verifier
check_iat -k iak_pub.pem -t PSA-2.0.0-token token.cbor -p   # デコードして表示・検証
検証サービスが確かめること① COSE を解析し CBOR の主張を取り出す② UEID から登録済み IAK 公開鍵を引く③ 署名を検証ECDSA P-256 + SHA-256④ nonce = 送ったチャレンジ再送防止⑤ lifecycle = SECUREDデバッグ状態を拒否⑥ sw components の各ハッシュが許可リストに既知の正しいファームウェアか⑦ 結果をアプリケーションサーバへ参照実装: Veraison / iat-verifier装置側は 1 関数だが、価値は検証側で生まれる
署名・nonce・ライフサイクル・ハッシュの 4 点を確認して初めて意味がある

6. 使いどころ

場面アテステーションの使い方
クラウドへの初回登録(オンボーディング)トークンを提示し、正規のハードウェア・ファームウェアであることを確認してから証明書を発行する(iotsec 編 12 章のプロビジョニング)
定期的な健全性確認接続のたびに、または日次でトークンを取り、ファームウェアが改変されていないか確認する
OTA 後の確認更新後のトークンで、新しいイメージのハッシュが報告されることを確認する
機密データの配布条件「SECURED 状態で、バージョン X 以上のとき」だけ鍵を配る

トークンに秘密は入っていない。装置 ID とハッシュと署名だけである。 だから TLS で暗号化していない経路で送っても、内容が漏れて困ることはない(誰が何を動かしているかは分かる)。

7. 手を動かす

トークンの構造を見る

8. 仕様書に逃がす

項目参照先
トークンの完全な定義PSA Certified Attestation API 1.0 仕様書、および IETF(インターネット標準化団体)RFC 9711(EAT)と draft-tschofenig-rats-psa-token(PSA プロファイル)
CBOR / COSE の詳細RFC 8949 / RFC 9052
対称鍵版(COSE_Mac0)Attestation API 仕様書の "Symmetric key" 節。SYMMETRIC_INITIAL_ATTESTATION ビルドオプション
Measured boot の仕組みTF-M の tfm_boot_data / MCUboot の shared data。iotsec 編 05 章

この章のポイント