ゲートキーパー サブシステムは、高信頼実行環境(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 の対応するコンポーネント(ゲートキーパー)にリクエストを送信します。
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)を直接使用できます。