デバイスのセキュリティ ステータスを把握する

アプリのセキュリティは、基盤となる OS のセキュリティに左右されます。リスクを軽減するには、デバイスのセキュリティ体制をリアルタイムで評価し、デバイス(特にコア システム コンポーネント)が完全にアップデートされ、Android セキュリティに関する公開情報で公開されている脆弱性から保護されているかどうかを判断して、セキュリティに影響するオペレーションを制限する必要があります。デバイスのセキュリティ状態を可視化することで、アプリが高リスクのオペレーションを実行する前に保護対策を講じることができます。

AndroidX Security State は、Android 搭載デバイスのセキュリティ状態への統一されたアクセスを提供する Jetpack ライブラリです。AOSP API と Android の公開された脆弱性フィードを組み合わせて、セキュリティ パッチ レベル(SPL)だけでなく、包括的で正確かつ実用的なデバイスのセキュリティ ステータスを提供します。

アーキテクチャの概要

次の図は、AndroidX Security State ライブラリが、デバイス上のプラットフォーム プロパティ、プロセス間通信(IPC)更新プロバイダ、公開されている脆弱性フィードを、単一のまとまりのある API セットに統合する方法を示しています。

AndroidX Security State ライブラリは、デバイス上のプラットフォーム プロパティ、IPC アップデート プロバイダ、公開されている脆弱性フィードを統一された API のセットに統合します。

セキュリティ パッチの状態とコンポーネント

セキュリティ状態ライブラリを使用する主な利点は、セキュリティ パッチ レベル(SPL)をコンポーネント レベルで細かく提供できることです。Android では、長年にわたり、Google Play システム アップデート(プロジェクト Mainline)にモジュールが追加されてきました。これらのモジュールは、標準のシステム無線(OTA)アップデートとは別に、Google Play を使用して更新され、重要なセキュリティ修正が異なるペースで提供されます。

同様に、セキュリティ パッチ レベルは通常、必要な最小の汎用カーネル イメージ(GKI)バージョンを義務付けますが、カーネル アップデートは実際にはシステム スケジュールよりも先に進む可能性があります。これらのカーネル固有の修正は、システムのプライマリ SPL 文字列で完全にキャプチャされない可能性があります。

透明性を高めるため、セキュリティ状態ライブラリは、3 つのコンポーネントすべてに対して個別に有効なパッチレベルを提供します。

  • システム(COMPONENT_SYSTEM): 標準のセキュリティ パッチレベル フィールドから導出された、標準の OS とシステムのセキュリティ ステータスを表します。
  • システム Mainline モジュール(COMPONENT_SYSTEM_MODULES): 特定のモジュールのリリース バージョンから導出された、モジュラー システム コンポーネント(Google Play システム アップデート)のセキュリティ ステータスを表します。
  • カーネル(COMPONENT_KERNEL): デバイスのカーネルのセキュリティ ステータスを表します。カーネル バージョン文字列から直接導出されます。

コンポーネントに応じて、ライブラリは最大 3 つのパッチレベル情報を提供します。

  • デバイス SPL(DSPL): デバイスで実行されている個々のコンポーネントの現在のパッチレベル。ネットワーク リクエストなしで同期的にクエリされます。システムと Mainline はレポート カレンダーの日付(DateBasedSecurityPatchLevel)をレポートしますが、カーネルはリリース バージョン(VersionedSecurityPatchLevel、例: 5.15.148)をレポートします。
  • 公開済み SPL(PSPL): Android のセキュリティに関する公開情報とオープンソースの脆弱性(OSV)データベース レポートで正式に公開されているベースライン。アプリは、デバイス SPL によって提供される日付またはバージョン文字列を比較することで、コンプライアンスを評価できます。
  • 利用可能な SPL(ASPL): IPC を使用して非同期でクエリされた、アップデート プロバイダから利用可能な保留中のアップデートがデバイスにあるかどうかを示します。Android の Google Play システム アップデート機能は Mainline モジュールの可用性を提供し、OEM OTA クライアントはシステム アップデートの可用性を提供します。カーネル アップデートはオペレーティング システム イメージにバンドルされているため、カーネル アップデートの可用性は COMPONENT_SYSTEM を使用して評価されます。

セキュリティに配慮した機能をゲートする

デバイス SPL(DSPL)、利用可能な SPL(ASPL)、公開済み SPL(PSPL)の情報を組み合わせて、セキュリティに配慮した機能のゲーティングに関するコンテキスト上の判断を行うことができます。たとえば、DSPL と ASPL を比較して、ユーザーがまだインストールしていない新しいセキュリティ パッチが利用可能かどうかを判断し、支払いなどの機密性の高い操作や認証情報の登録を開始する前に、更新を求めるメッセージを表示できます。また、areCvesPatched() を使用して、特定の高リスクの脆弱性がデバイスで修正されているかどうかを確認してから、脆弱なサブシステムを呼び出すこともできます。たとえば、近接ベースの支払いまたはデータ共有を承認する前に、重大な NFC または Bluetooth の脆弱性をチェックします。

プラットフォーム バージョンに関する考慮事項

セキュリティ状態の特定の機能は基盤となるプラットフォーム アーキテクチャに依存しており、Android の古いバージョンでは利用できません。

  • Android 11(API レベル 30)以上: 掲示板で公開されたカーネル LTS バージョンやアップデートの利用可能性(ASPL)クエリなど、すべてのコンポーネントを完全にサポートします。
  • Android 10(API レベル 29): システムとシステム モジュールのパッチレベルをサポートします。ただし、Android セキュリティに関する公開情報で汎用カーネル イメージ(GKI)のトラッキングとカーネル LTS ターゲットが始まったのは Android 11 からであるため、公開情報で公開されたカーネル バージョンは利用できません。デバイス上のカーネル バージョンはローカルで読み取ることができます。
  • Android 9(API レベル 28)以前: Android 10 の制限に加えて、Android 10 より前のモジュラー システム コンポーネント(プロジェクト Mainline)は存在しませんでした。システム モジュールの SPL が利用できない場合、getDeviceSecurityPatchLevel(COMPONENT_SYSTEM_MODULES) の呼び出しは安全にベースラインの Unix エポック日付(1970 年 1 月 1 日)にフォールバックします。

アップデートの利用可能性と利用可能な SPL(ASPL)について

デバイスのセキュリティ ポスチャーを判断するには、個々のコンポーネントで保留中のセキュリティ アップデートが利用可能かどうかを知る必要があります。Android デバイスは、システム無線(OTA)アップデートやモジュラー コンポーネントの Google Play システム アップデートなど、複数の異なる配信メカニズムを介してセキュリティ アップデートを受信します。

アプリが特定のコンポーネントの ASPL を取得するには、対応する更新プロバイダがその情報を公開する必要があります。これらのさまざまな更新ソースを調整するために、クライアント アプリケーションは AndroidX Security State ライブラリを使用してセキュリティ ステータスをクエリし、オンデバイス更新クライアントはコンパニオン AndroidX Security State Provider ライブラリを使用して利用可能なセキュリティ パッチレベル(ASPL)を公開します。

Google は、Mainline モジュールのアップデート情報をすべての GMS Android デバイスに提供します。また、Google OTA クライアント(GOTA)を使用するデバイスのシステム OTA 情報も提供します。

次の図は、クライアント アプリケーションが AndroidX Security State ライブラリを使用して、Google Play システム アップデートとシステム OTA アップデートの両方でセキュリティ ポスチャーをクエリする方法を示しています。

クライアント アプリケーションは、AndroidX Security State ライブラリを使用して、Google Play システム アップデートとシステム OTA アップデートの両方でセキュリティ ポスチャーをクエリします。

依存関係を追加する

AndroidX Security State への依存関係を追加するには、Google Maven リポジトリをプロジェクトに含める必要があります。アプリの build.gradle.kts ファイルまたは build.gradle ファイルに依存関係を追加します。

Kotlin

// Kotlin DSL (build.gradle.kts)
dependencies {
    implementation("androidx.security:security-state:1.1.0")
}

Groovy

// Groovy DSL (build.gradle)
dependencies {
    implementation "androidx.security:security-state:1.1.0"
}

申告済みの権限のマトリックス

ターゲット API AndroidManifest.xml で必要な権限 運用上の注意事項
getDeviceSecurityPatchLevel() なし ローカルのネイティブ システム プロパティとパッケージ メタデータを同期的に読み取ります。
fetchAvailableSecurityPatchLevel()
queryAllAvailableUpdates()
なし デバイス上の IPC を使用して通信します。ライブラリはデバイス上の信頼できるコンポーネントのみをクエリします(更新プロバイダが特権の READ_PRIVILEGED_PHONE_STATE 権限を保持している必要があります)。そのため、アプリは受信した情報の信頼性を確信できます。
createVulnerabilityReportUrl()
loadVulnerabilityReport()
getPublishedSecurityPatchLevel()
areCvesPatched()
isDeviceFullyUpdated()
android.permission.INTERNET 公開 OSV レポートを取得するために必要です。loadVulnerabilityReport() を使用してメモリに読み込まれると、クエリ メソッドはネットワーク アクセスなしでローカルで実行されます。

ライブラリを初期化する

Android の Context を使用して SecurityPatchState を初期化します。

Kotlin

import androidx.security.state.SecurityPatchState

val securityPatchState = SecurityPatchState(context)

Java

import androidx.security.state.SecurityPatchState;

SecurityPatchState securityPatchState = new SecurityPatchState(context);

context のみで初期化すると、ネットワーク依存関係なしでオフライン デバイスのパッチレベルにすぐにアクセスできます。(可用性クエリの更新は、デバイス上のプロバイダと非同期で通信します)。

アプリが CVE 準拠を評価し、OSV 脆弱性レポートをすでにメモリに読み込んでいる場合、Kotlin 呼び出し元は JSON 文字列を Kotlin のコンストラクタに直接渡すことができます。

val securityPatchState = SecurityPatchState(context, vulnerabilityReportJsonString = jsonString)

Java の場合、または起動後にレポートを非同期で読み込む場合は、コンテキストで初期化して securityPatchState.loadVulnerabilityReport(jsonString) を呼び出します。

デバイスのパッチレベルを確認する

デバイスのパッチレベルを同期的にクエリします。

Kotlin

val deviceSpl = securityPatchState.getDeviceSecurityPatchLevel(SecurityPatchState.COMPONENT_SYSTEM)
val mainlineSpl = securityPatchState.getDeviceSecurityPatchLevel(SecurityPatchState.COMPONENT_SYSTEM_MODULES)
val kernelVersion = securityPatchState.getDeviceSecurityPatchLevel(SecurityPatchState.COMPONENT_KERNEL)

// Gatekeeping check: compare against a required baseline patch date
val requiredSpl = SecurityPatchState.DateBasedSecurityPatchLevel.fromString("2026-01-01")
if (deviceSpl < requiredSpl) {
    // Restrict access to sensitive features or guide user to update
}

Java

SecurityPatchState.SecurityPatchLevel deviceSpl = securityPatchState.getDeviceSecurityPatchLevel(SecurityPatchState.COMPONENT_SYSTEM);
SecurityPatchState.SecurityPatchLevel mainlineSpl = securityPatchState.getDeviceSecurityPatchLevel(SecurityPatchState.COMPONENT_SYSTEM_MODULES);
SecurityPatchState.SecurityPatchLevel kernelVersion = securityPatchState.getDeviceSecurityPatchLevel(SecurityPatchState.COMPONENT_KERNEL);

// Gatekeeping check: compare against a required baseline patch date
SecurityPatchState.SecurityPatchLevel requiredSpl = SecurityPatchState.DateBasedSecurityPatchLevel.fromString("2026-01-01");
if (deviceSpl.compareTo(requiredSpl) < 0) {
    // Restrict access to sensitive features or guide user to update
}

保留中のシステム アップデートを確認する

アプリは、信頼できるオンデバイス アップデート プロバイダ(通常は Google Play システム アップデートと OEM OTA クライアント)と非同期で通信することで、保留中のアップデートの利用可能性を評価できます。

  • fetchAvailableSecurityPatchLevel(): 指定されたコンポーネントの有効な利用可能な SPL を返します(新しい更新がない場合は現在のデバイス SPL にフォールバックします)。これは、デバイス SPL および公開済み SPL と比較するために使用できます。
  • queryAllAvailableUpdates(): デバイス上のすべての信頼できるアップデート プロバイダを検出し、データのソースと鮮度に関するメタデータとともに、きめ細かい UpdateCheckResult を返します。

例 1: 保留中の更新についてユーザーに通知する

セキュリティ アップデートが利用可能になったときにユーザーに通知するには、fetchAvailableSecurityPatchLevel() を使用します(たとえば、Settings.ACTION_SYSTEM_UPDATE_SETTINGS を起動するなど)。

Kotlin

import androidx.lifecycle.lifecycleScope
import androidx.security.state.SecurityPatchState
import kotlinx.coroutines.launch

// Pattern A: Prompt users for pending updates (with automatic offline fallback)
lifecycleScope.launch {
    val currentSpl = securityPatchState.getDeviceSecurityPatchLevel(SecurityPatchState.COMPONENT_SYSTEM)
    val availableSpl = securityPatchState.fetchAvailableSecurityPatchLevel(SecurityPatchState.COMPONENT_SYSTEM)
    if (availableSpl > currentSpl) {
        // Prompt the user to install pending updates in system settings
    }
}

Java

Java では、非同期メソッドは ListenableFuture を返し、ContextCompat.getMainExecutor() を使用してコールバックをルーティングします。

import androidx.core.content.ContextCompat;
import androidx.security.state.SecurityPatchState;
import androidx.security.state.SecurityPatchState.SecurityPatchLevel;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;

// Pattern A: Prompt users for pending updates (with automatic offline fallback)
SecurityPatchLevel currentSpl = securityPatchState.getDeviceSecurityPatchLevel(SecurityPatchState.COMPONENT_SYSTEM);
ListenableFuture<SecurityPatchLevel> availableSpl = securityPatchState.fetchAvailableSecurityPatchLevelAsync(
    SecurityPatchState.COMPONENT_SYSTEM,
    SecurityPatchState.UPDATE_INFO_SERVICE_BINDING_TIMEOUT_MS
);

Futures.addCallback(availableSpl, new FutureCallback<SecurityPatchLevel>() {
    @Override
    public void onSuccess(SecurityPatchLevel available) {
        if (available.compareTo(currentSpl) > 0) {
            // Prompt the user to install pending updates in system settings
        }
    }

    @Override
    public void onFailure(Throwable t) {
        // Handle timeout or IPC communication error
    }
}, ContextCompat.getMainExecutor(context));

例 2: プロバイダの詳細なステータスを検査する

queryAllAvailableUpdates() を使用して、個々のプロバイダ、タイムスタンプ、UpdateInfo レコードを検査します。

Kotlin

import androidx.lifecycle.lifecycleScope
import androidx.security.state.SecurityPatchState
import kotlinx.coroutines.launch

// Pattern B: Inspect detailed provider status
lifecycleScope.launch {
    val updateResults = securityPatchState.queryAllAvailableUpdates()
    for (result in updateResults) {
        val provider = result.providerPackageName
        val lastCheck = result.lastCheckTimeMillis
        val updates = result.updates // List<UpdateInfo>
    }
}

Java

import androidx.core.content.ContextCompat;
import androidx.security.state.SecurityPatchState;
import androidx.security.state.UpdateCheckResult;
import androidx.security.state.UpdateInfo;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.List;

// Pattern B: Inspect detailed provider status
ListenableFuture<List<UpdateCheckResult>> updateResults = securityPatchState.queryAllAvailableUpdatesAsync(
    SecurityPatchState.UPDATE_INFO_SERVICE_BINDING_TIMEOUT_MS
);

Futures.addCallback(updateResults, new FutureCallback<List<UpdateCheckResult>>() {
    @Override
    public void onSuccess(List<UpdateCheckResult> results) {
        for (UpdateCheckResult result : results) {
            String provider = result.getProviderPackageName();
            long lastCheck = result.getLastCheckTimeMillis();
            List<UpdateInfo> updates = result.getUpdates();
        }
    }

    @Override
    public void onFailure(Throwable t) {
        // Handle error
    }
}, ContextCompat.getMainExecutor(context));

脆弱性の解決を検証し、CVE を確認する

CVE の緩和策を評価したり、アップデートの全体的なコンプライアンスを確認したり、公開済みのセキュリティ パッチレベル(PSPL)を検査したりするには、まず、SecurityPatchState の API リファレンスで説明されているように、OSV 脆弱性レポートを取得して読み込む必要があります。

Kotlin

// Load vulnerability report
val reportUrl = SecurityPatchState.createVulnerabilityReportUrl()
// ... download JSON string from reportUrl ...
securityPatchState.loadVulnerabilityReport(jsonString)

// Check overall update compliance against published bulletin
val isFullyUpdated = securityPatchState.isDeviceFullyUpdated()

val cves = listOf("CVE-2019-9501", "CVE-2020-3699", "CVE-2024-0016")
val isPatched = securityPatchState.areCvesPatched(cves)

// Get a list of all patched CVEs for a specific component and SPL
val deviceSpl = securityPatchState.getDeviceSecurityPatchLevel(SecurityPatchState.COMPONENT_SYSTEM)
val patchedSystemCVEs = securityPatchState.getPatchedCves(SecurityPatchState.COMPONENT_SYSTEM, deviceSpl)

// Inspect published SPL and kernel LTS target versions from the bulletin
val publishedSystemSpl = securityPatchState.getPublishedSecurityPatchLevel(SecurityPatchState.COMPONENT_SYSTEM)
val kernelLtsTargets = securityPatchState.getPublishedSecurityPatchLevel(SecurityPatchState.COMPONENT_KERNEL)

Java

// Load vulnerability report
Uri reportUrl = SecurityPatchState.createVulnerabilityReportUrl();
// ... download JSON string from reportUrl ...
securityPatchState.loadVulnerabilityReport(jsonString);

// Check overall update compliance against published bulletin
boolean isFullyUpdated = securityPatchState.isDeviceFullyUpdated();

List<String> cves = Arrays.asList("CVE-2019-9501", "CVE-2020-3699", "CVE-2024-0016");
boolean isPatched = securityPatchState.areCvesPatched(cves);

// Get a list of all patched CVEs for a specific component and SPL
SecurityPatchState.SecurityPatchLevel deviceSpl = securityPatchState.getDeviceSecurityPatchLevel(SecurityPatchState.COMPONENT_SYSTEM);
Map<SecurityPatchState.Severity, Set<String>> patchedSystemCVEs =
    securityPatchState.getPatchedCves(SecurityPatchState.COMPONENT_SYSTEM, deviceSpl);

// Inspect published SPL and kernel LTS target versions from the bulletin
List<SecurityPatchState.SecurityPatchLevel> publishedSystemSpl =
    securityPatchState.getPublishedSecurityPatchLevel(SecurityPatchState.COMPONENT_SYSTEM);
List<SecurityPatchState.SecurityPatchLevel> kernelLtsTargets =
    securityPatchState.getPublishedSecurityPatchLevel(SecurityPatchState.COMPONENT_KERNEL);

脆弱性レポートを読み込む

  1. ローカル キャッシュ保存と WorkManager による更新: Android セキュリティに関する公開情報は毎月公開されるため、ダウンロードした JSON を内部ストレージcontext.filesDir)にキャッシュ保存し、WorkManager を使用して定期的なバックグラウンド タスク(できれば 24 ~ 48 時間ごと)をスケジュールしてレポートを更新し、アプリの起動時にローカル キャッシュから同期的に読み取ります。
  2. バックグラウンド スレッド: CVE レコードの解析中にメインの UI スレッドがブロックされないように、バックグラウンド スレッド(Kotlin の Dispatchers.IO または Java の Executor)で loadVulnerabilityReport() を呼び出します。
  3. カーネル LTS の評価: getPatchedCves(COMPONENT_KERNEL, ...) を呼び出すと IllegalArgumentException がスローされ、areCvesPatched() はカーネル CVE を評価しません。代わりに、デバイスのカーネル バージョンと、getPublishedSecurityPatchLevel(COMPONENT_KERNEL) が返す Android 共通カーネル LTS ターゲットを比較して、カーネルのセキュリティを評価します。このとき、メジャー ブランチとマイナー ブランチが一致している必要があります(たとえば、5.15.140 カーネルと 5.15.159 ターゲットを比較します)。

参考情報

デバイスのセキュリティ状態について詳しくは、次のリソースをご覧ください。

ドキュメント

API リファレンス