ゲートキーパー

ゲートキーパー サブシステムは、Trusted Execution Environment(TEE)でデバイスのパターン認証またはパスワード認証を実行します。ゲートキーパーはハードウェア格納型の秘密鍵を使用した HMAC によってパスワードを登録、確認します。さらに、ゲートキーパーは確認の試行が連続して失敗することを抑制し、指定されたタイムアウトと連続試行失敗回数に基づいてサービス リクエストを拒否します。

ユーザーがパスワードを確認すると、ゲートキーパーは TEE で導出された共有シークレットを使用して認証証明書に署名し、ハードウェア格納型キーストアに送信します。つまりゲートキーパーの証明書は、認証にバインドされた鍵(アプリが作成した鍵など)をアプリが使用できるように解放可能であることをキーストアに通知します。

アーキテクチャ

ゲートキーパーの主要コンポーネントは次の 3 つです。

  • gatekeeperd(ゲートキーパー デーモン)。プラットフォームに依存しないロジックを含み、GateKeeperService Java インターフェースに対応する C++ バインダー サービス。
  • ゲートキーパー ハードウェア抽象化レイヤ(HAL)hardware/libhardware/include/hardware/gatekeeper.h の HAL インターフェースと実装モジュール。
  • ゲートキーパー(TEE)。TEE で gatekeeperd に相当するもの。ゲートキーパーの TEE ベースの実装。

ゲートキーパーでは、ゲートキーパー HAL(具体的には hardware/libhardware/include/hardware/gatekeeper.h の関数)と TEE 固有のゲートキーパー コンポーネント(鍵の作成と鍵へのアクセス、署名の計算に使用される純粋仮想関数を含む system/gatekeeper/include/gatekeeper/gatekeeper.h ヘッダー ファイルに一部基づく)を実装する必要があります。

LockSettingsService は、Android OS の gatekeeperd デーモンにバインダー経由でリクエストを送信します。次に gatekeeperd デーモンは、TEE の対応するコンポーネント(ゲートキーパー)にリクエストを送信します。

ゲートキーパーのフロー
図 1. ゲートキーパーによる認証のデータフローの概要

gatekeeperd デーモンは、Android フレームワーク API に HAL へのアクセス権を提供し、キーストアへのデバイス認証のレポートに関与します。 gatekeeperd デーモンは独自のプロセスで実行され、システム サーバーとは分離されています。

HAL 実装

gatekeeperd デーモンは、パスワード認証のために HAL を使用して gatekeeperd デーモンに対応する TEE のコンポーネントとやり取りします。HAL 実装では、blob の署名(登録)と確認が可能である必要があります。すべての実装では、パスワード確認が成功するたびに生成される認証トークン(AuthToken)が標準形式に準拠していることが求められます。AuthToken の内容とセマンティックについては、AuthToken の形式をご覧ください。

hardware/libhardware/include/hardware/gatekeeper.h ヘッダー ファイルの実装では、enroll 関数と verify 関数を実装する必要があります。

  • enroll 関数はパスワード blob を取得して署名し、署名をハンドルとして返します。enroll の呼び出しによって返された blob は、system/gatekeeper/include/gatekeeper/password_handle.h に示された構造である必要があります。
  • verify 関数は、指定のパスワードによって生成された署名と、登録されたパスワード ハンドルを比較して一致することを確認します。

登録と確認に使用される鍵は変更せずに、デバイスの起動ごとに再導出可能にしておく必要があります。

Trusty などの実装

Trusty オペレーティング システムは、TEE 環境向けの Google オープンソースの信頼できる OS で、ゲートキーパーの承認済みの実装を含んでいます。ただし、ハードウェア格納型の鍵と、停止状態でも時を刻む安全な単調クロックにアクセスできれば、どの TEE OS でもゲートキーパーの実装に使用できます。

Trusty は内部 IPC システムを使用して、Keymaster とゲートキーパーの Trusty 実装(Trusty ゲートキーパー)の間で共有シークレットを直接やり取りします。この共有シークレットは、パスワード確認の証明書を提供するためにキーストアに送信される AuthToken の署名に使用されます。Trusty ゲートキーパーは使用のたびに鍵を Keymaster にリクエストし、値を保持したりキャッシュしたりすることはありません。実装では、セキュリティを侵害しない限り自由にこのシークレットを共有できます。

パスワードの登録と確認に使用される HMAC 鍵は、ゲートキーパーでのみ導出および保持されます。

Android には、デバイス固有のルーティンを追加するだけで完了する、ゲートキーパーの一般的な C++ 実装が用意されています。TEE 用のデバイス固有のコードで TEE ゲートキーパーを実装するには、system/gatekeeper/include/gatekeeper/gatekeeper.h の関数とコメントをご覧ください。 TEE ゲートキーパーの場合、適切な実装の主な条件は次のとおりです。

  • ゲートキーパー HAL への準拠。
  • 返される AuthToken は、AuthToken の仕様(認証を参照)に従って記述される必要があります。
  • TEE ゲートキーパーは、必要に応じて TEE IPC を介して鍵をリクエストするか、値の有効なキャッシュを常に保持することで、HMAC 鍵を Keymaster と共有できる必要があります。

ユーザーのセキュア ID(SID)

ユーザー SID は TEE でユーザーを表すもので、Android ユーザー ID との強い関連性はありません。ユーザーが以前のパスワードを入力せずに新しいパスワードを登録するたびに、暗号論的擬似乱数生成ツール(PRNG)で SID が生成されます。これは信頼できない再登録と呼ばれ、通常は Android フレームワークが許可することはありません。ユーザーが有効な以前のパスワードを入力した場合は、信頼できる再登録が行われます。この場合、ユーザー SID は新しいパスワード ハンドルに移行され、バインドされている鍵が保持されます。

パスワードが登録されたときに、パスワード ハンドルでパスワードとともにユーザー SID に対して HMAC が行われます。

ユーザー SID は、verify 関数によって返される AuthToken に書き込まれ、認証にバインドされたすべてのキーストア鍵に関連付けられます(AuthToken の形式とキーストアについて詳しくは、認証をご覧ください)。enroll 関数の信頼できない呼び出しによってユーザー SID が変更されるため、そのパスワードにバインドされている鍵が無効になります。攻撃者は Android OS を制御すればデバイスのパスワードを変更できますが、ルート保護された機密鍵はその途中で破棄されます。

リクエストの抑制

ゲートキーパーは、ユーザー認証情報に対するブルートフォースの実行を安全に抑制できる必要があります。hardware/libhardware/include/hardware/gatekeeper.h に示すように、HAL はミリ秒単位のタイムアウトを返します。これにより、タイムアウトが経過するまでゲートキーパーを再呼び出ししないようクライアントに通知します。ゲートキーパーは、タイムアウトが保留されている場合はリクエストを処理しません。

ゲートキーパーは、ユーザー パスワードを確認する前に失敗カウンタを書き込む必要があります。パスワードの確認が成功した場合は、失敗カウンタがクリアされます。これにより、verify 呼び出しの実行後に埋め込み MMC(eMMC)を無効にして抑制を回避する攻撃が阻止されます。enroll 関数もユーザー パスワード(入力された場合)を確認するため、同じ方法で抑制される必要があります。

デバイスでサポートされている場合は、セキュア ストレージに失敗カウンタを書き込むことを強くおすすめします。デバイスでファイルベースの暗号化がサポートされていない場合や、セキュア ストレージの速度が遅すぎる場合は、実装で RPMB(Replay Protected Memory Block)を直接使用できます。