ハードウェア格納型キーストア

システムオンチップ(SoC)に信頼できる実行環境(TEE)が実現されているため、Android デバイスでは、ハードウェア格納型の強力なセキュリティ サービスを、Android OS、プラットフォーム サービス、さらにはサードパーティ アプリに提供できます。Android 固有の拡張機能をお探しのデベロッパーは、android.security.keystore にアクセスしてください。

6.0 より前の Android には、Keymaster Hardware Abstraction Layer(HAL)のバージョン 0.2 と 0.3 で提供される、シンプルなハードウェア格納型の暗号化サービス API がすでに存在し、キーストアにより、デジタル署名と検証オペレーションに加えて、非対称署名鍵ペアの生成とインポートが可能でした。この機能は多くのデバイスにすでに実装されていますが、多くのセキュリティ目標は、署名 API だけでは簡単に実現できません。Android 6.0 のキーストアは、Keystore API を拡張し、より幅広い機能を提供します。

Android 6.0 では、キーストアにより、対称暗号プリミティブ、AES と HMAC、ハードウェア格納型鍵のためのアクセス制御システムが追加されました。アクセス制御は、鍵の生成時に指定され、鍵の存続期間にわたって適用されます。ユーザーが認証された後ではじめて、指定された目的または指定された暗号パラメータでのみ、鍵を使用できるように制限できます。詳細については、承認タグのページ関数のページをご覧ください。

Android 6.0 のキーストアは、暗号プリミティブの範囲を拡張しただけでなく、次の機能も追加しました。

  • 鍵の使用を制限し、鍵の不正使用によるセキュリティ侵害のリスクを削減するための使用制御スキーム
  • 指定されたユーザーとクライアント、定義された期間に鍵の使用を制限するためのアクセス制御スキーム

Android 7.0 では、Keymaster 2 の鍵構成証明とバージョン バインディングのサポートが追加されました。鍵構成証明は、鍵の詳細な説明とそのアクセス制御を含む公開鍵証明書を提供し、鍵がセキュア ハードウェアに存在することとその構成をリモートで検証できるようにします。

バージョン バインディングは、オペレーティング システムとパッチレベルのバージョンに鍵をバインドします。これにより、攻撃者が古いバージョンのシステムまたは TEE ソフトウェアに脆弱性を見つけても、デバイスを脆弱なバージョンにロールバックして新しいバージョンで作成された鍵を使用することができなくなります。さらに、特定のバージョンおよびパッチレベルの鍵が、新しいバージョンまたはパッチレベルにアップグレードされたデバイスで使用された場合、鍵は使用可能になる前にアップグレードされ、鍵の以前のバージョンは無効化されます。デバイスをアップグレードすると鍵はデバイスとともに「アップグレード」されますが、デバイスを以前のリリースに戻すとその鍵は使用できなくなります。

Android 8.0 では、Keymaster 3 は古いスタイルの C 構造の Hardware Abstraction Layer(HAL)から、新しいハードウェア インターフェース定義言語(HIDL)の定義から生成された C++ HAL インターフェースに移行しました。変更の一部として、引数の型の多くが変更されましたが、新しい型とメソッドは古い型と HAL の struct メソッドに 1 対 1 で対応しています。詳細については、関数のページをご覧ください。

このインターフェースの改訂に加えて、Android 8.0 では Keymaster 2 の構成証明機能が拡張され、ID 構成証明がサポートされました。ID 構成証明は、デバイスのシリアル番号、製品名、電話 ID(IMEI / MEID)など、ハードウェア識別子の強力な証明機能を提供する、オプションの限定的なメカニズムです。この追加機能を実装するために、Android 8.0 では ASN.1 構成証明スキーマを変更して ID 構成証明を追加しました。Keymaster の実装では、関連するデータアイテムを取得するセキュアな方法を見つけるとともに、機能をセキュアかつ恒久的に無効にするメカニズムを定義する必要があります。

Android 9 では以下のアップデートが行われました。

  • Keymaster 4 へのアップデート
  • 埋め込みセキュア エレメントのサポート
  • セキュア鍵のインポートのサポート
  • 3DES 暗号化のサポート
  • boot.img と system.img で別々にバージョンを設定して個々に更新できるようにするためのバージョン バインディングの変更

用語集

ここでは、キーストアのコンポーネントとそれらの関係の概要を簡単に説明します。

AndroidKeystore は、アプリがキーストア機能にアクセスするために使用する Android Framework API およびコンポーネントです。標準の Java Cryptography Architecture API の拡張として実装され、アプリの固有のプロセス空間で実行される Java コードで構成されます。AndroidKeystore は、リクエストをキーストア デーモンに転送することで、キーストアの動作に対するアプリ リクエストを実行します。

キーストア デーモンは、Android システムのデーモンであり、Binder API を通じてすべてのキーストア機能へのアクセスを提供します。キーストアで格納はできるが使用と公開はできないように暗号化された、実際の秘密鍵マテリアルを含む「鍵 blob」を格納する役割を果たします。

keymasterd は、Keymaster TA へのアクセスを提供する HIDL サーバーです(この名称は標準化されておらず、コンセプトを示す目的で使用されています)。

Keymaster TA(TA: 信頼できるアプリ)は、セキュア コンテキストで実行されるソフトウェアであり、多くの場合 ARM SoC の TrustZone 内にあります。すべてのセキュアなキーストア オペレーションを提供する、未処理の鍵マテリアルにアクセスする、鍵のすべてのアクセス制御条件を検証するなどの機能を果たします。

LockSettingsService は、パスワード認証と指紋認証の両方のユーザー認証を行う Android システム コンポーネントです。キーストアの一部ではありませんが、キーストアの鍵オペレーションは多くの場合ユーザー認証を必要とするため、キーストアと関連があります。LockSettingsService は、ゲートキーパー TA およびフィンガープリント TA とやり取りしてキーストア デーモンに提供する認証トークンを取得します。認証トークンは最終的に Keymaster TA アプリによって使用されます。

ゲートキーパー TA(TA: 信頼できるアプリ)は、セキュア コンテキストで実行される別のコンポーネントで、ユーザー パスワードの認証と認証トークンの生成を行います。認証トークンは、特定の時点で特定のユーザーが認証されたことを Keymaster TA に証明するために使用されます。

フィンガープリント TA(TA: 信頼できるアプリ)は、セキュア コンテキストで実行される別のコンポーネントで、ユーザーの指紋認証と認証トークンの生成を行います。認証トークンは、特定の時点で特定のユーザーが認証されたことを Keymaster TA に証明するために使用されます。

アーキテクチャ

Android Keystore API と基盤となる Keymaster HAL は、基本的な(しかし十分な)暗号プリミティブのセットを提供し、アクセス制御されたハードウェア格納型鍵を使用するプロトコルの実装を可能にします。

Keymaster HAL は、キーストア サービスがハードウェア格納型暗号サービスを提供するために使用する、動的読み込みが可能な OEM 提供のライブラリです。セキュリティを確保するため、HAL 実装はユーザー空間で、さらにはカーネル空間でも、機密性の高いオペレーションを実行しません。機密性の高いオペレーションは、カーネル インターフェースでアクセスされるセキュア プロセッサに委任されます。つまり、アーキテクチャは次のようになります。

Keymaster へのアクセス

図 1.Keymaster へのアクセス

Android デバイス内では、Keymaster HAL の「クライアント」は複数のレイヤ(アプリ、フレームワーク、キーストア デーモンなど)で構成されますが、このドキュメントの目的からは無視できます。つまり、ここに記載されている Keymaster HAL API は下位レベルの API であり、プラットフォーム内部のコンポーネントによって使用され、アプリ デベロッパーには公開されません。上位レベルの API は、Android デベロッパー サイトで説明されています。

Keymaster HAL の目的はセキュリティに対応したアルゴリズムを実装することではなく、セキュア環境へのリクエストをマーシャリングおよびマーシャリング解除することに限られます。ワイヤ形式は実装で定義されます。

以前のバージョンとの互換性

Keymaster 1 HAL は、以前リリースされた HAL(Keymaster 0.2 および 0.3 など)と完全に非互換です。古い Keymaster HAL で起動する Android 5.0 以前のデバイスでの相互運用性を高めるために、既存のハードウェア ライブラリへの呼び出しを Keymaster 1 HAL に実装するアダプターが、キーストアによって提供されています。つまり、Keymaster 1 HAL のすべての機能を提供することはできません。特に、サポートされるアルゴリズムは RSA と ECDSA のみであり、鍵認証の適用はすべてアダプターにより非セキュア環境で実行されます。

Keymaster 2 では、get_supported_* メソッドが削除されて、finish()メソッドが入力を受け取れるようになったことで、HAL インターフェースがさらに簡素化されました。これにより、入力を一度にまとめて利用できる場合は TEE へのラウンド トリップの回数が減り、AEAD 復号の実装が簡単になります。

Android 8.0 では、Keymaster 3 は古いスタイルの C 構造の HAL から、新しいハードウェア インターフェース定義言語(HIDL)の定義から生成された C++ HAL インターフェースに移行しました。新しいスタイルの HAL 実装は、生成された IKeymasterDevice クラスをサブクラス化して純粋な仮想メソッドを実装することにより、作成されます。変更の一部として、引数の型の多くが変更されましたが、新しい型とメソッドは古い型と HAL の struct メソッドに 1 対 1 で対応しています。

HIDL の概要

ハードウェア インターフェース定義言語(HIDL)は、ハードウェア インターフェースを指定するための、言語に依存しない実装メカニズムを提供します。現在、HIDL ツールは C++ および Java インターフェースの生成をサポートしています。信頼できる実行環境(TEE)の実装ではほとんどの場合 C++ ツールのほうが便利であると思われるため、このドキュメントでは C++ 表現についてのみ説明します。

HIDL インターフェースは、次のように表現されるメソッドのセットで構成されます。

  methodName(INPUT ARGUMENTS) generates (RESULT ARGUMENTS);

さまざまな事前定義された型が存在し、HAL では新しい列挙型と構造型を定義できます。HIDL の詳細については、リファレンス セクションをご覧ください。

Keymaster 3 の IKeymasterDevice.hal のメソッドの例を次に示します。

generateKey(vec<KeyParameter> keyParams)
        generates(ErrorCode error, vec<uint8_t> keyBlob,
                  KeyCharacteristics keyCharacteristics);

これは、keymaster2 HAL の次のメソッドと同等です。

keymaster_error_t (*generate_key)(
        const struct keymaster2_device* dev,
        const keymaster_key_param_set_t* params,
        keymaster_key_blob_t* key_blob,
        keymaster_key_characteristics_t* characteristics);

HIDL バージョンでは、dev 引数は暗黙的に指定されるため、削除されます。params 引数は、key_parameter_t オブジェクトの配列を参照するポインタを含む構造体ではなくなり、KeyParameterオブジェクトを含む vec(ベクター)になりました。戻り値は、鍵 blob の uint8_t 値のベクターを含む「generates」句内にリストされます。

HIDL コンパイラによって生成される C++ 仮想メソッドは次のようになります。

Return<void> generateKey(const hidl_vec<KeyParameter>& keyParams,
                         generateKey_cb _hidl_cb) override;

ここで、generateKey_cb は次のように定義される関数ポインタです。

std::function<void(ErrorCode error, const hidl_vec<uint8_t>& keyBlob,
                   const KeyCharacteristics& keyCharacteristics)>

つまり、generateKey_cb は、generate 句にリストされる戻り値を受け取る関数です。HAL 実装クラスは、この generateKey メソッドをオーバーライドし、generateKey_cb 関数ポインタを呼び出してオペレーションの結果を呼び出し元に返します。関数ポインタの呼び出しは同期呼び出しであることにご注意ください。呼び出し元は generateKey を呼び出し、generateKey は指定された関数ポインタを呼び出します。関数ポインタの実行が完了すると generateKey 実装に制御を戻し、それから呼び出し元に戻ります。

詳細な例については、hardware/interfaces/keymaster/3.0/default/KeymasterDevice.cpp のデフォルト実装をご覧ください。 デフォルト実装では、古いスタイルの keymaster0、keymaster1、または keymaster2 の HAL を装備したデバイスに対する下位互換性が提供されます。

アクセス制御

キーストアのアクセス制御の最も基本的なルールは、各アプリに独自の名前空間があることです。ただし、どのようなルールにも例外はあります。キーストアには、特定のシステム コンポーネントが他の特定の名前空間にアクセスできるようにする、ハードコードされたマップがあります。これは、あるコンポーネントが別の名前空間を完全に制御できるようにするという点で、かなり要領の悪いやり方です。さらに、キーストアのクライアントとしてのベンダー コンポーネントの問題もあります。現在のところ、WPA サプリカントなど、ベンダー コンポーネントの名前空間を確立する方法はありません。

ベンダー コンポーネントに対応し、ハードコードの例外なくアクセス制御を一般化するために、Keystore 2.0 ではドメインと SELinux 名前空間を導入しています。

キーストア ドメイン

キーストア ドメインを使用すると、名前空間と UID を分離できます。キーストアで鍵にアクセスするクライアントは、アクセスするドメイン、名前空間、エイリアスを指定する必要があります。このタプルと呼び出し元の ID に基づいて、呼び出し元がどの鍵にアクセスするのかと、適切な権限を持っているかどうかを判断できます。

鍵へのアクセス方法を制御する 5 つのドメイン パラメータが導入されました。これにより、鍵記述子の名前空間パラメータのセマンティクスと、アクセス制御の実施方法を制御します。

  • DOMAIN_APP: アプリドメインが以前の動作に対応します。Java Keystore SPI は、デフォルトでこのドメインを使用します。このドメインを使用する場合、名前空間引数は無視され、代わりに呼び出し元の UID が使用されます。このドメインへのアクセスは、SELinux ポリシーで keystore_key クラスのキーストア ラベルで制御します。
  • DOMAIN_SELINUX: このドメインは、SELinux ポリシーに名前空間のラベルがあることを示します。名前空間パラメータが検索されてターゲット コンテキストに変換され、keystore_key クラスの呼び出し元 SELinux コンテキストに対して権限チェックが行われます。所定のオペレーションに対して権限が設定されると、鍵の検索に完全なタプルが使用されます。
  • DOMAIN_GRANT: 権限付与ドメインは、名前空間パラメータが権限付与 ID であることを示します。エイリアス パラメータは無視されます。SELinux チェックは、権限付与の作成時に実施されます。さらにアクセス制御は、呼び出し元 UID がリクエストされた権限付与の付与対象 UID と一致するかどうかのみをチェックします。
  • DOMAIN_KEY_ID: このドメインは、名前空間パラメータが一意の鍵 ID であることを示します。鍵自体は、DOMAIN_APP または DOMAIN_SELINUX で作成されている可能性があります。権限チェックは、ドメイン、名前空間、エイリアス タプルによって blob が読み込まれた場合と同様に、domainnamespace が鍵データベースから読み込まれた後に実施されます。鍵 ID ドメインの基本原理は連続性です。エイリアスで鍵にアクセスする場合、新しい鍵が生成またはインポートされてそのエイリアスにバインドされている可能性があるため、後続の呼び出しは他の鍵で行われる可能性があります。ただし、鍵 ID は変更されません。そのため、エイリアスを使用してキーストア データベースから鍵 ID を一度読み込んだ後にその鍵 ID で鍵を使用する場合、鍵 ID が存在する限り、同じ鍵であることを確認できます。この機能はアプリ デベロッパーには公開されていません。代わりに Android Keystore SPI 内で使用されており、安全でない方法で同時に使用された場合でも、一貫性のあるエクスペリエンスが提供されます。
  • DOMAIN_BLOB: blob ドメインは、呼び出し元が blob を独自に管理することを示します。これは、データ パーティションがマウントされる前にキーストアにアクセスする必要があるクライアントに使用されます。鍵 blob は、鍵記述子の blob フィールドに含まれます。

SELinux ドメインを使用すると、設定ダイアログなどのシステム コンポーネントで共有できる非常に特殊なキーストア名前空間へのアクセス権を、ベンダー コンポーネントに付与できます。

keystore_key の SELinux ポリシー

名前空間ラベルは、keystore2_key_context ファイルを使用して構成します。
このファイルの各行は、数値の名前空間 ID を SELinux ラベルにマッピングします。次に例を示します。

# wifi_key is a keystore2_key namespace intended to be used by wpa supplicant and
# Settings to share keystore keys.
102            u:object_r:wifi_key:s0

このようにして新しい鍵の名前空間をセットアップしたら、適切なポリシーを追加することで名前空間へのアクセスを許可できます。たとえば、wpa_supplicant が新しい名前空間の鍵を取得して使用できるようにするには、次の行を hal_wifi_supplicant.te に追加します。

allow hal_wifi_supplicant wifi_key:keystore2_key { get, use };

新しい名前空間をセットアップした後は、AndroidKeyStore をほぼ通常どおり使用できます。唯一の違いは、名前空間 ID を指定する必要があることです。キーストアから鍵を読み込んだり、キーストアに鍵をインポートする場合は、AndroidKeyStoreLoadStoreParameter を使用して名前空間 ID を指定します。次に例を示します。

import android.security.keystore2.AndroidKeyStoreLoadStoreParameter;
import java.security.KeyStore;

KeyStore keystore = KeyStore.getInstance("AndroidKeyStore");
keystore.load(new AndroidKeyStoreLoadStoreParameter(102));

特定の名前空間に鍵を生成するには、KeyGenParameterSpec.Builder#setNamespace(): を使用して名前空間 ID を指定する必要があります。

import android.security.keystore.KeyGenParameterSpec;
KeyGenParameterSpec.Builder specBuilder = new KeyGenParameterSpec.Builder();
specBuilder.setNamespace(102);

Keystore 2.0 SELinux 名前空間を構成するには、次のコンテキスト ファイルを使用できます。競合を避けるため、各パーティションには 10,000 個の名前空間 ID の異なる予約範囲があります。

パーティション 範囲 構成ファイル
システム 0 ... 9,999

/system/etc/selinux/keystore2_key_contexts, /plat_keystore2_key_contexts
拡張システム 10,000 ... 19,999

/system_ext/etc/selinux/system_ext_keystore2_key_contexts, /system_ext_keystore2_key_contexts
プロダクト 20,000 ... 29,999

/product/etc/selinux/product_keystore2_key_contexts, /product_keystore2_key_contexts
ベンダー 30,000 ... 39,999

/vendor/etc/selinux/vendor_keystore2_key_contexts, /vendor_keystore2_key_contexts

クライアントは、SELinux ドメインと目的の仮想名前空間(この場合は "wifi_key")を数値 ID でリクエストすることで、鍵をリクエストします。

そのうえ、次の名前空間が定義されています。特別なルールを置き換える場合、次の表は、以前に対応していた UID を示します。

名前空間 ID SEPolicy ラベル UID 説明
0 su_key なし スーパー ユーザー鍵。userdebug ビルドと eng ビルドのテストにのみ使用します。ユーザービルドには関係ありません。
1 shell_key なし シェルで使用できる名前空間。ほとんどはテストに使用しますが、ユーザービルドでもコマンドラインから使用できます。
100 vold_key なし vold による使用向け。
101 odsing_key なし デバイス上の署名デーモンが使用します。
102 wifi_key AID_WIFI(1010) wpa_supplicant を含む Android の Wifi シンボルシステムが使用します。
120 resume_on_reboot_key AID_SYSTEM(1000) 再起動時の再開をサポートするために Android のシステム サーバーが使用します。

アクセス ベクトル

SELinux クラス keystore_key はかなり古くなっており、一部の権限(verifysign など)は意味を失っています。以下に、Keystore 2.0 が適用する新しい権限セット keystore2_key を示します。

権限 説明
delete キーストアから鍵を削除するときに確認します。
get_info 鍵のメタデータがリクエストされたときに確認します。
grant ターゲット コンテキストで鍵に対する権限付与を作成するには、呼び出し元にこの権限が必要です。
manage_blob 呼び出し元は指定された SELinux 名前空間で DOMAIN_BLOB を使用し、blob を独自に管理できます。これは特に vold で役立ちます。
rebind この権限は、エイリアスが新しい鍵に再バインドされるかどうかを制御します。これは挿入に必要であり、以前にバインドされた鍵が削除されることを意味します。基本的には挿入権限ですが、キーストアのセマンティックの方が適切にキャプチャされます。
req_forced_op この権限を持つクライアントは、プルーニングできないオペレーションを作成できます。すべてのオペレーション スロットがプルーニングできないオペレーションによって占められない限り、オペレーションの作成は失敗しません。
update 鍵のサブコンポーネントを更新するために必要です。
use 鍵マテリアル(署名、暗号化、復号など)を使用する Keymint オペレーションを作成するときに確認します。
use_dev_id デバイス ID 構成証明などのデバイス識別情報を生成するときに必要です。

さらに、SELinux セキュリティ クラス keystore2 では、鍵固有でないキーストア権限のセットが分割されました。

権限 説明
add_auth Gatekeeper や BiometricsManager などの認証プロバイダが認証トークンを追加するために必要です。
clear_ns 以前は clear_uid でした。この権限では、名前空間の所有者以外が名前空間内のすべての鍵を削除できます。
list システムで所有権や認証の境界などのさまざまなプロパティによって鍵を列挙するために必要です。呼び出し元が独自の名前空間を列挙する場合、この権限は必要ありません。その場合は get_info 権限が対応します。
lock この権限でキーストアをロックできます。つまり、認証にバインドされた鍵が使用できなくなり、作成できなくなるように、マスター鍵を除去できます。
reset この権限により、キーストアを出荷時の設定にリセットし、Android OS の機能にとって重要ではない鍵をすべて削除できます。
unlock この権限は、認証にバインドされた鍵のマスター鍵をロック解除しようとするときに必要です。