ゲートキーパー

ゲートキーパー サブシステムは、高信頼実行環境(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 であれば、どの 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 の制御権を奪ってデバイスのパスワードを変更できますが、root 保護された機密鍵はその途中で破棄されます。

リクエストの抑制

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

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

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