Android 12 以降、Android ランタイム(ART)モジュールは Mainline モジュールとなっています。モジュールの更新には、bootclasspath jar とシステム サーバーの事前(AOT)コンパイル アーティファクトの再構築が必要になる場合があります。これらのアーティファクトはセキュリティが重要となるため、Android 12 ではオンデバイス署名という機能を使用してアーティファクトの改ざんを防ぎます。このページでは、オンデバイス署名アーキテクチャと、Android のその他のセキュリティ機能との相互作用について説明します。
設計の概要
オンデバイス署名には 2 つの主要なコンポーネントがあります。
odrefresh
: ART Mainline モジュールの一部です。ランタイム アーティファクトの生成を担います。既存のアーティファクトをインストールされたバージョンの ART モジュール、bootclasspath jar、システム サーバー jar と照合し、アーティファクトが最新か、再生成が必要かを判断します。再生成が必要な場合は、odrefresh
がアーティファクトを生成して保存します。odsign
: Android プラットフォームの一部であるバイナリです。アーリーブート中の/data
パーティションがマウントされた直後に実行されます。その主な役目は、odrefresh
を呼び出して生成や更新が必要なアーティファクトがないかチェックすることです。新たなアーティファクトや更新後のアーティファクトがodrefresh
によって生成されると、odsign
がハッシュ関数を計算します。こうしたハッシュ計算によって得られた値は、ファイル ダイジェストと呼ばれます。すでに存在するアーティファクトについて、odsign
は既存のアーティファクトのダイジェストがodsign
によって以前に計算されたダイジェストと一致しているかを検証します。こうすることでアーティファクトが改ざんされていないことを確認できます。
ファイルのダイジェストが一致しないなどのエラーが発生した場合、odrefresh
と odsign
は /data
の既存のアーティファクトをすべて破棄し、再生成を試みます。それが失敗した場合は、JIT モードにフォールバックします。
odrefresh
と odsign
は dm-verity
によって保護され、Android の確認付きブートチェーンの一部となっています。
fs-verity によるファイル ダイジェストの計算
fs-verity は Linux カーネルの機能で、マークルツリーを使用したファイルデータの検証を行います。ファイルの fs-verity を有効にすると、ファイル システムは SHA-256 ハッシュを使用してファイルのデータを基にマークルツリーを構築します。マークルツリーはファイルと一緒に非表示の場所に保存され、ファイルは読み取り専用に設定されます。fs-verity は、ファイルが読み込まれる際にオンデマンドで自動的にファイルのデータをマークルツリーと照合して検証します。fs-verity はマークルツリーのルートハッシュを fs-verity ファイル ダイジェストという値として利用可能にし、ファイルから読み込まれたデータがこのファイル ダイジェストと一致していることを確認します。
odsign
は fs-verity を使用し、起動時に行われるオンデバイスでコンパイルされたアーティファクトの暗号認証を最適化することで、起動パフォーマンスを改善します。アーティファクトが生成されると、odsign
がアーティファクトの fs-verity を有効にします。アーティファクトの検証時には、odsign
はファイル ハッシュ全体ではなく fs-verity ファイル ダイジェストを検証します。こうすることで起動時にアーティファクトのデータ全体を読み込んでハッシュする必要がなくなります。その代わりアーティファクトのデータは、使用時に fs-verity によってブロックごとにオンデマンドでハッシュされます。
カーネルが fs-verity をサポートしていないデバイスでは、odsign
が代わりにユーザー空間でファイル ダイジェストを計算します。odsign
は fs-verity と同じマークルツリーに基づくハッシュ アルゴリズムを使用するため、ダイジェストはどちらの場合でも同じになります。fs-verity は Android 11 以降を搭載したすべてのデバイスで必須とされています。
ファイル ダイジェストの保存
odsign
はアーティファクトのファイル ダイジェストを odsign.info
という別個のファイルに保存します。odsign.info
が改ざんされていないことを確認するため、odsign.info
は重要なセキュリティ プロパティを持つ署名鍵で署名されます。具体的には、署名鍵は信頼できるコードだけが実行されるアーリーブート中にのみ生成され、使用できます。詳細については信頼できる署名鍵をご覧ください。
ファイル ダイジェストの検証
起動時に odrefresh
が既存のアーティファクトが最新だと判断すると、odsign
はファイルが生成時から改ざんされていないことを確認します。そのために odsign
はファイル ダイジェストを検証します。まず、odsign.info
の署名が検証されます。署名が有効な場合、odsign
は各ファイルのダイジェストが odsign.info
の対応するダイジェストと一致するか検証します。
信頼できる署名鍵
Android 12 では、次のセキュリティ上の懸念事項に対処するためのブートステージ鍵という新しいキーストア機能が導入されています。
- 攻撃者が独自のバージョンの
odsign.info
に署名するために署名鍵を使用することをどうやって防ぐか。 - 攻撃者が独自のバージョンの
odsign.info
に署名するために独自の署名鍵を生成して使用することをどうやって防ぐか。
ブートステージ鍵は、Android の起動サイクルを複数のレベルに分割し、暗号的に鍵の生成と使用を指定されたレベルに関連付けます。odsign
は、dm-verity
によって保護され、信頼できるコードのみが実行されている初期レベルで、署名鍵を生成します。
各ブートステージ レベルには、0 からマジック番号 1000000000 までの番号が振られます。Android の起動プロセス中に、init.rc
からシステム プロパティを設定することでブートレベルを上げることができます。次の例では、ブートレベルを 10 に設定します。
setprop keystore.boot_level 10
キーストアのクライアントは、特定のブートレベルに関連付けられた鍵を生成できます。たとえば、ブートレベル 10 のキーを生成した場合、そのキーはブートレベル 10 の場合にのみ使用できます。
odsign
はブートレベル 30 を使用し、生成する署名鍵はそのブートレベルに関連付けられます。odsign
は、鍵を使用してアーティファクトに署名する前に、その鍵がブートレベル 30 に関連付けられていることを検証します。
これにより、このセクションで挙げた 2 つの攻撃を防ぐことができます。
- 攻撃者は生成された鍵を使用できません。その理由は、攻撃者が悪意のあるコードを実行できるようになる頃には、ブートレベルが 30 より大きくなり、キーストアにより鍵を使用する操作が拒否されるためです。
- 攻撃者は新しい鍵を生成できません。その理由は、攻撃者が悪意のあるコードを実行できるようになる頃には、ブートレベルが 30 より大きくなり、キーストアにより、そのブートレベルの新しい鍵を生成することが拒否されるためです。攻撃者がブートレベル 30 に関連付けられていない新しい鍵を作成すると、
odsign
によりその鍵が拒否されます。
キーストアが、ブートレベルの適切な強制適用を保証しています。以降のセクションでは、Keymaster のバージョンごとに、これを行う方法について説明します。
Keymaster 4.0 の実装
ブートステージ鍵の実装の処理方法は、Keymaster のバージョンによって異なります。Keymaster 4.0 TEE/Strongbox を搭載しているデバイスでは、Keymaster は次のように実装を処理します。
- 初回起動時、キーストアは、
MAX_USES_PER_BOOT
タグを1
に設定した対称鍵 K0 を生成します。これは、この鍵が起動ごとに 1 回だけ使用できるということを意味します。 - 起動中、ブートレベルを上げると、そのブートレベルの新しい鍵を HKDF 関数
Ki+i=HKDF(Ki, "some_fixed_string")
を使用して K0 から生成できます。たとえば、ブートレベル 0 からブートレベル 10 に移行すると、HKDF が 10 回呼び出され、K0 から K10 が生成されます。 ブートレベルが変更されると、以前のブートレベルのキーはメモリから消去され、以前のブートレベルに関連付けられた鍵は使用できなくなります。
キー K0 は
MAX_USES_PER_BOOT=1
キーです。つまり、少なくとも 1 回のブートレベルの遷移(最後のブートレベルへの遷移)が常に発生するため、起動の後半ではそのキーを使用することはできません。
odsign
などのキーストアのクライアントがブートレベル i
で鍵の生成をリクエストすると、その blob は鍵 Ki
で暗号化されます。Ki
はブートレベル i
の後では利用できないため、この鍵を後のブートステージで作成したり復号したりすることはできません。
Keymaster 4.1 と KeyMint 1.0 の実装
Keymaster 4.1 と KeyMint 1.0 の実装は、Keymaster 4.0 の実装とほぼ同じです。主な違いは、K0 は MAX_USES_PER_BOOT
キーではなく、Keymaster 4.1 で導入された EARLY_BOOT_ONLY
キーだという点です。EARLY_BOOT_ONLY
キーは、信頼できないコードが実行されていない起動の初期フェーズでのみ使用できます。これにより、さらなるレベルの保護が実現します。Keymaster 4.0 の実装では、ファイル システムと SELinux を侵害した攻撃者は、キーストア データベースを変更して、アーティファクトの署名に使用する独自の MAX_USES_PER_BOOT=1
キーを生成できます。Keymaster 4.1 と KeyMint 1.0 の実装では、EARLY_BOOT_ONLY
鍵はアーリーブート中にのみ生成できるため、このような攻撃は不可能です。
信頼できる署名鍵の公開コンポーネント
odsign
は、キーストアから署名鍵の公開鍵コンポーネントを取得します。ただし、キーストアは、対応する秘密鍵を保持する TEE/SE からは、その公開鍵を取得しません。代わりに、それ自体のディスク上のデータベースから公開鍵を取得します。つまり、ファイル システムを侵害した攻撃者がキーストア データベースを変更し、攻撃者の管理下にある公開鍵 / 秘密鍵ペアの一部である公開鍵を、このデータベースに追加する可能性があります。
この攻撃を防ぐため、odsign
は署名鍵と同じブートレベルの HMAC キーを追加で生成します。署名鍵を作成する際は、odsign
はこの HMAC キーを使用して公開鍵の署名を生成し、ディスクに保存します。以降の起動では、署名鍵の公開鍵を取得する際に、HMAC キーを使用して、ディスク上の署名が取得した公開鍵の署名と一致することを確認します。一致する場合、HMAC キーはアーリーブート レベルでのみ使用でき、攻撃者は生成できないため、その公開鍵は信頼できます。