From 762b48e839f7489e2c39f7cab6b628df91c20e06 Mon Sep 17 00:00:00 2001 From: Davinci9196 Date: Wed, 22 Oct 2025 17:47:44 +0800 Subject: [PATCH 1/3] Vending: Added PI access management --- .../microg/gms/settings/SettingsContract.kt | 4 + .../microg/gms/settings/SettingsProvider.kt | 4 + .../microg/gms/vending/IntegrityVisitData.kt | 65 +++++++++ .../src/huawei/AndroidManifest.xml | 3 + .../gms/ui/PlayIntegrityManageFragment.kt | 85 ++++++++++++ .../org/microg/gms/ui/VendingFragment.kt | 7 +- .../microg/gms/vending/VendingPreferences.kt | 30 ++++ .../src/main/res/navigation/nav_settings.xml | 9 ++ .../src/main/res/values-zh-rCN/strings.xml | 7 + .../src/main/res/values/strings.xml | 5 + .../res/xml/preferences_play_integrity.xml | 28 ++++ .../com/android/vending/VendingPreferences.kt | 23 ++++ .../android/finsky/IntegrityExtensions.kt | 62 +++++++-- .../ExpressIntegrityService.kt | 45 +++++- .../integrityservice/IntegrityService.kt | 130 ++++++++++-------- 15 files changed, 427 insertions(+), 80 deletions(-) create mode 100644 play-services-base/core/src/main/kotlin/org/microg/gms/vending/IntegrityVisitData.kt create mode 100644 play-services-core/src/main/kotlin/org/microg/gms/ui/PlayIntegrityManageFragment.kt create mode 100644 play-services-core/src/main/res/xml/preferences_play_integrity.xml diff --git a/play-services-base/core/src/main/kotlin/org/microg/gms/settings/SettingsContract.kt b/play-services-base/core/src/main/kotlin/org/microg/gms/settings/SettingsContract.kt index 8a9ff68474..fb5d072031 100644 --- a/play-services-base/core/src/main/kotlin/org/microg/gms/settings/SettingsContract.kt +++ b/play-services-base/core/src/main/kotlin/org/microg/gms/settings/SettingsContract.kt @@ -279,6 +279,8 @@ object SettingsContract { const val ASSET_DEVICE_SYNC = "vending_device_sync" const val APPS_INSTALL = "vending_apps_install" const val APPS_INSTALLER_LIST = "vending_apps_installer_list" + const val PLAY_INTEGRITY = "vending_play_integrity" + const val PLAY_INTEGRITY_APP_LIST = "vending_play_integrity_apps" val PROJECTION = arrayOf( LICENSING, @@ -289,6 +291,8 @@ object SettingsContract { ASSET_DEVICE_SYNC, APPS_INSTALL, APPS_INSTALLER_LIST, + PLAY_INTEGRITY, + PLAY_INTEGRITY_APP_LIST ) } diff --git a/play-services-base/core/src/main/kotlin/org/microg/gms/settings/SettingsProvider.kt b/play-services-base/core/src/main/kotlin/org/microg/gms/settings/SettingsProvider.kt index fd05f7b639..76946282de 100644 --- a/play-services-base/core/src/main/kotlin/org/microg/gms/settings/SettingsProvider.kt +++ b/play-services-base/core/src/main/kotlin/org/microg/gms/settings/SettingsProvider.kt @@ -369,6 +369,8 @@ class SettingsProvider : ContentProvider() { Vending.SPLIT_INSTALL -> getSettingsBoolean(key, false) Vending.APPS_INSTALL -> getSettingsBoolean(key, false) Vending.APPS_INSTALLER_LIST -> getSettingsString(key, "") + Vending.PLAY_INTEGRITY -> getSettingsBoolean(key, false) + Vending.PLAY_INTEGRITY_APP_LIST -> getSettingsString(key, "") else -> throw IllegalArgumentException("Unknown key: $key") } } @@ -386,6 +388,8 @@ class SettingsProvider : ContentProvider() { Vending.ASSET_DEVICE_SYNC -> editor.putBoolean(key, value as Boolean) Vending.APPS_INSTALL -> editor.putBoolean(key, value as Boolean) Vending.APPS_INSTALLER_LIST -> editor.putString(key, value as String) + Vending.PLAY_INTEGRITY -> editor.putBoolean(key, value as Boolean) + Vending.PLAY_INTEGRITY_APP_LIST -> editor.putString(key, value as String) else -> throw IllegalArgumentException("Unknown key: $key") } } diff --git a/play-services-base/core/src/main/kotlin/org/microg/gms/vending/IntegrityVisitData.kt b/play-services-base/core/src/main/kotlin/org/microg/gms/vending/IntegrityVisitData.kt new file mode 100644 index 0000000000..c537a7692f --- /dev/null +++ b/play-services-base/core/src/main/kotlin/org/microg/gms/vending/IntegrityVisitData.kt @@ -0,0 +1,65 @@ +/** + * SPDX-FileCopyrightText: 2025 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.vending + +import org.json.JSONException +import org.json.JSONObject + +class IntegrityVisitData(var allowed: Boolean, val packageName: String, val pkgSignSha256: String) { + var lastVisitTime: Long? = null + var lastVisitResult: String? = null + + override fun toString(): String { + return JSONObject() + .put(ALLOWED, allowed) + .put(PACKAGE_NAME, packageName) + .put(SIGNATURE, pkgSignSha256) + .put(LAST_VISIT_TIME, lastVisitTime) + .put(LAST_VISIT_RESULT, lastVisitResult) + .toString() + } + + companion object { + private const val PACKAGE_NAME = "packageName" + private const val ALLOWED = "allowed" + private const val SIGNATURE = "signature" + private const val LAST_VISIT_TIME = "lastVisitTime" + private const val LAST_VISIT_RESULT = "lastVisitResult" + + private fun parse(jsonString: String): IntegrityVisitData? { + try { + val json = JSONObject(jsonString) + return IntegrityVisitData( + json.getBoolean(ALLOWED), + json.getString(PACKAGE_NAME), + json.getString(SIGNATURE) + ).apply { + lastVisitTime = json.getLong(LAST_VISIT_TIME) + lastVisitResult = json.getString(LAST_VISIT_RESULT) + } + } catch (e: JSONException) { + return null + } + } + + fun loadDataSet(content: String): Set { + return content.split("|").mapNotNull { parse(it) }.toSet() + } + + fun updateDataSetString(channelList: Set, channel: IntegrityVisitData): String { + val channelData = channelList.find { it.packageName == channel.packageName && it.pkgSignSha256 == channel.pkgSignSha256 } + val newChannelList = if (channelData != null) { + channelData.allowed = channel.allowed + channelData.lastVisitTime = channel.lastVisitTime + channelData.lastVisitResult = channel.lastVisitResult + channelList + } else { + channelList + channel + } + return newChannelList.let { it -> it.joinToString(separator = "|") { it.toString() } } + } + } +} \ No newline at end of file diff --git a/play-services-core/src/huawei/AndroidManifest.xml b/play-services-core/src/huawei/AndroidManifest.xml index 26aa9013ea..ce8aabcce8 100644 --- a/play-services-core/src/huawei/AndroidManifest.xml +++ b/play-services-core/src/huawei/AndroidManifest.xml @@ -58,5 +58,8 @@ + \ No newline at end of file diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/PlayIntegrityManageFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/PlayIntegrityManageFragment.kt new file mode 100644 index 0000000000..56eea118f0 --- /dev/null +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/PlayIntegrityManageFragment.kt @@ -0,0 +1,85 @@ +/** + * SPDX-FileCopyrightText: 2025 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.ui + +import android.os.Bundle +import androidx.lifecycle.lifecycleScope +import androidx.preference.Preference +import androidx.preference.PreferenceCategory +import androidx.preference.PreferenceFragmentCompat +import androidx.preference.SwitchPreference +import com.google.android.gms.R +import org.microg.gms.utils.getApplicationLabel +import org.microg.gms.vending.IntegrityVisitData +import org.microg.gms.vending.VendingPreferences +import java.text.SimpleDateFormat +import java.util.Date + +class PlayIntegrityManageFragment : PreferenceFragmentCompat() { + private lateinit var switchBarPreference: SwitchBarPreference + private lateinit var apps: PreferenceCategory + private lateinit var appsNone: Preference + + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + addPreferencesFromResource(R.xml.preferences_play_integrity) + + switchBarPreference = preferenceScreen.findPreference("pref_play_integrity_enabled") ?: switchBarPreference + apps = preferenceScreen.findPreference("pref_play_integrity_apps") ?: apps + appsNone = preferenceScreen.findPreference("pref_play_integrity_apps_none") ?: appsNone + + switchBarPreference.setOnPreferenceChangeListener { _, newValue -> + val appContext = requireContext().applicationContext + lifecycleScope.launchWhenResumed { + if (newValue is Boolean) { + VendingPreferences.setPlayIntegrityEnabled(appContext, newValue) + } + updateContent() + } + true + } + } + + override fun onResume() { + super.onResume() + updateContent() + } + + fun updateContent() { + val appContext = requireContext().applicationContext + lifecycleScope.launchWhenResumed { + switchBarPreference.isChecked = VendingPreferences.isPlayIntegrityEnabled(appContext) + val visitAppContent = VendingPreferences.getPlayIntegrityAppList(appContext) + val visitAppSet = IntegrityVisitData.loadDataSet(visitAppContent) + val visitAppViews = visitAppSet.mapNotNull { + runCatching { + SwitchPreference(appContext).apply { + key = "pref_permission_apps_${it.packageName}" + title = appContext.packageManager.getApplicationLabel(it.packageName) + icon = appContext.packageManager.getApplicationIcon(it.packageName) + isChecked = it.allowed + summary = "Last: ${it.lastVisitResult} \n${it.lastVisitTime?.let { time -> SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(Date(time)) }}" + setOnPreferenceChangeListener { _, newValue -> + lifecycleScope.launchWhenResumed { + if (newValue is Boolean) { + val content = IntegrityVisitData.updateDataSetString(visitAppSet, it.apply { this.allowed = newValue }) + VendingPreferences.setPlayIntegrityAppList(appContext, content) + } + } + true + } + } + }.getOrNull() + } + apps.removeAll() + for (visitAppView in visitAppViews) { + apps.addPreference(visitAppView) + } + if (visitAppViews.isEmpty()) { + apps.addPreference(appsNone) + } + } + } +} \ No newline at end of file diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/VendingFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/VendingFragment.kt index acd224c188..cb2d2f2bbd 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/VendingFragment.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/VendingFragment.kt @@ -128,6 +128,7 @@ class VendingFragment : PreferenceFragmentCompat() { override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { menu.add(0, MENU_INSTALL_MANAGED, 0, R.string.pref_app_install_settings_title) + menu.add(0, MENU_PI_MANAGE, 0, R.string.menu_play_integrity_manage) super.onCreateOptionsMenu(menu, inflater) } @@ -137,12 +138,16 @@ class VendingFragment : PreferenceFragmentCompat() { findNavController().navigate(requireContext(), R.id.openVendingInstallSettings) true } - + MENU_PI_MANAGE -> { + findNavController().navigate(requireContext(), R.id.openPiManageFragment) + true + } else -> super.onOptionsItemSelected(item) } } companion object { private const val MENU_INSTALL_MANAGED = Menu.FIRST + private const val MENU_PI_MANAGE = Menu.FIRST + 1 const val PREF_LICENSING_ENABLED = "vending_licensing" const val PREF_LICENSING_PURCHASE_FREE_APPS_ENABLED = "vending_licensing_purchase_free_apps" const val PREF_SPLIT_INSTALL_ENABLED = "vending_split_install" diff --git a/play-services-core/src/main/kotlin/org/microg/gms/vending/VendingPreferences.kt b/play-services-core/src/main/kotlin/org/microg/gms/vending/VendingPreferences.kt index 4f95a41b18..f0ec443550 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/vending/VendingPreferences.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/vending/VendingPreferences.kt @@ -129,4 +129,34 @@ object VendingPreferences { put(SettingsContract.Vending.APPS_INSTALLER_LIST, content) } } + + @JvmStatic + fun isPlayIntegrityEnabled(context: Context): Boolean { + val projection = arrayOf(SettingsContract.Vending.PLAY_INTEGRITY) + return SettingsContract.getSettings(context, SettingsContract.Vending.getContentUri(context), projection) { c -> + c.getInt(0) != 0 + } + } + + @JvmStatic + fun setPlayIntegrityEnabled(context: Context, enabled: Boolean) { + SettingsContract.setSettings(context, SettingsContract.Vending.getContentUri(context)) { + put(SettingsContract.Vending.PLAY_INTEGRITY, enabled) + } + } + + @JvmStatic + fun getPlayIntegrityAppList(context: Context): String { + val projection = arrayOf(SettingsContract.Vending.PLAY_INTEGRITY_APP_LIST) + return SettingsContract.getSettings(context, SettingsContract.Vending.getContentUri(context), projection) { c -> + c.getString(0) + } + } + + @JvmStatic + fun setPlayIntegrityAppList(context: Context, content: String) { + SettingsContract.setSettings(context, SettingsContract.Vending.getContentUri(context)) { + put(SettingsContract.Vending.PLAY_INTEGRITY_APP_LIST, content) + } + } } \ No newline at end of file diff --git a/play-services-core/src/main/res/navigation/nav_settings.xml b/play-services-core/src/main/res/navigation/nav_settings.xml index 4e202752d1..65613175ca 100644 --- a/play-services-core/src/main/res/navigation/nav_settings.xml +++ b/play-services-core/src/main/res/navigation/nav_settings.xml @@ -172,6 +172,15 @@ + + + + diff --git a/play-services-core/src/main/res/values-zh-rCN/strings.xml b/play-services-core/src/main/res/values-zh-rCN/strings.xml index 4e9a452a5e..e94899c5d8 100644 --- a/play-services-core/src/main/res/values-zh-rCN/strings.xml +++ b/play-services-core/src/main/res/values-zh-rCN/strings.xml @@ -156,6 +156,13 @@ microG GmsCore 内置一套自由的 SafetyNet 实现,但是官方服务器要 使用 SafetyNet 的应用 清除近期的 SafetyNet 请求 最近使用于%1$s + Integrity API 管理 + Google Play Integrity + 允许 API 认证 + 访问 Play Integrity API 的应用 + "Play Integrity API 是 Google 用来替代 SafetyNet 的新一代设备与应用完整性验证服务。某些应用会出于安全考虑或是防篡改目的而使用 Play Integrity API。 + +microG Companion 内置一套自由的 Play Integrity API 实现, 但是官方服务器存在校验返回数据安全的可能性。" 评估类型 响应状态 响应数据 diff --git a/play-services-core/src/main/res/values/strings.xml b/play-services-core/src/main/res/values/strings.xml index e1d7ecf16a..b8fb1f008a 100644 --- a/play-services-core/src/main/res/values/strings.xml +++ b/play-services-core/src/main/res/values/strings.xml @@ -242,6 +242,11 @@ Please set up a password, PIN, or pattern lock screen." Clear recent requests Last use: %1$s + Integrity API Managed + Google Play Integrity + Allow API authentication + Apps using Play Integrity API + "The Play Integrity API is Google's next-generation device and app integrity verification service, replacing SafetyNet. Some apps use the Play Integrity API for security or tamper prevention purposes. \n\nmicroG Companion includes a free implementation of the Play Integrity API, but the official server may verify the security of the returned data." Native Real Custom: %s diff --git a/play-services-core/src/main/res/xml/preferences_play_integrity.xml b/play-services-core/src/main/res/xml/preferences_play_integrity.xml new file mode 100644 index 0000000000..b2f686e083 --- /dev/null +++ b/play-services-core/src/main/res/xml/preferences_play_integrity.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + diff --git a/vending-app/src/main/java/com/android/vending/VendingPreferences.kt b/vending-app/src/main/java/com/android/vending/VendingPreferences.kt index 078e17d031..cc718e3d21 100644 --- a/vending-app/src/main/java/com/android/vending/VendingPreferences.kt +++ b/vending-app/src/main/java/com/android/vending/VendingPreferences.kt @@ -80,4 +80,27 @@ object VendingPreferences { put(SettingsContract.Vending.APPS_INSTALLER_LIST, content) } } + + @JvmStatic + fun isPlayIntegrityEnabled(context: Context): Boolean { + val projection = arrayOf(SettingsContract.Vending.PLAY_INTEGRITY) + return SettingsContract.getSettings(context, SettingsContract.Vending.getContentUri(context), projection) { c -> + c.getInt(0) != 0 + } + } + + @JvmStatic + fun getPlayIntegrityAppList(context: Context): String { + val projection = arrayOf(SettingsContract.Vending.PLAY_INTEGRITY_APP_LIST) + return SettingsContract.getSettings(context, SettingsContract.Vending.getContentUri(context), projection) { c -> + c.getString(0) + } + } + + @JvmStatic + fun setPlayIntegrityAppList(context: Context, content: String) { + SettingsContract.setSettings(context, SettingsContract.Vending.getContentUri(context)) { + put(SettingsContract.Vending.PLAY_INTEGRITY_APP_LIST, content) + } + } } \ No newline at end of file diff --git a/vending-app/src/main/kotlin/com/google/android/finsky/IntegrityExtensions.kt b/vending-app/src/main/kotlin/com/google/android/finsky/IntegrityExtensions.kt index 2610e00697..720bcf0da6 100644 --- a/vending-app/src/main/kotlin/com/google/android/finsky/IntegrityExtensions.kt +++ b/vending-app/src/main/kotlin/com/google/android/finsky/IntegrityExtensions.kt @@ -19,11 +19,15 @@ import android.security.keystore.KeyProperties import android.text.TextUtils import android.util.Base64 import android.util.Log +import androidx.core.content.edit +import com.android.vending.VendingPreferences import com.android.vending.buildRequestHeaders import com.android.vending.makeTimestamp import com.google.android.finsky.expressintegrityservice.ExpressIntegritySession import com.google.android.finsky.expressintegrityservice.IntermediateIntegrityResponseData import com.google.android.finsky.expressintegrityservice.PackageInformation +import com.google.android.finsky.model.IntegrityErrorCode +import com.google.android.finsky.model.StandardIntegrityException import com.google.android.gms.droidguard.DroidGuard import com.google.android.gms.droidguard.internal.DroidGuardResultsRequest import com.google.android.gms.tasks.await @@ -36,11 +40,14 @@ import com.google.crypto.tink.aead.AesGcmKeyManager import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import okio.ByteString +import okio.ByteString.Companion.decodeBase64 import okio.ByteString.Companion.encode import okio.ByteString.Companion.toByteString import org.microg.gms.common.Constants import org.microg.gms.profile.Build import org.microg.gms.profile.ProfileManager +import org.microg.gms.utils.getFirstSignatureDigest +import org.microg.gms.vending.IntegrityVisitData import org.microg.vending.billing.DEFAULT_ACCOUNT_TYPE import org.microg.vending.billing.GServices import org.microg.vending.billing.core.HttpClient @@ -97,6 +104,34 @@ private const val DEVICE_INTEGRITY_HARD_EXPIRATION = 432000L // 5 day const val INTERMEDIATE_INTEGRITY_HARD_EXPIRATION = 86400L // 1 day private const val TAG = "IntegrityExtensions" +fun callerAppToVisitData(context: Context, callingPackage: String?): IntegrityVisitData { + if (callingPackage.isNullOrEmpty()) { + throw StandardIntegrityException(IntegrityErrorCode.INTERNAL_ERROR, "Package name is empty") + } + val pkgSignSha256ByteArray = context.packageManager.getFirstSignatureDigest(callingPackage, "SHA-256") + if (pkgSignSha256ByteArray == null) { + throw StandardIntegrityException(IntegrityErrorCode.APP_NOT_INSTALLED, "$callingPackage signature is null") + } + val pkgSignSha256 = Base64.encodeToString(pkgSignSha256ByteArray, Base64.NO_WRAP) + Log.d(TAG, "callerToVisitData $callingPackage pkgSignSha256: $pkgSignSha256") + val playIntegrityAppList = VendingPreferences.getPlayIntegrityAppList(context) + val loadDataSet = IntegrityVisitData.loadDataSet(playIntegrityAppList) + if (loadDataSet.isEmpty() || loadDataSet.none { it.packageName == callingPackage && it.pkgSignSha256 == pkgSignSha256 }) { + return IntegrityVisitData(true, callingPackage, pkgSignSha256) + } + return loadDataSet.first { it.packageName == callingPackage && it.pkgSignSha256 == pkgSignSha256 } +} + +fun IntegrityVisitData.updateAppVisitContent(context: Context, visitTime: Long, visitResult: String) { + val playIntegrityAppList = VendingPreferences.getPlayIntegrityAppList(context) + val loadDataSet = IntegrityVisitData.loadDataSet(playIntegrityAppList) + val dataSetString = IntegrityVisitData.updateDataSetString(loadDataSet, apply { + lastVisitTime = visitTime + lastVisitResult = visitResult + }) + VendingPreferences.setPlayIntegrityAppList(context, dataSetString) +} + fun IntegrityRequestWrapper.getExpirationTime() = runCatching { val creationTimeStamp = deviceIntegrityWrapper?.creationTime ?: Timestamp(0, 0) val creationTime = (creationTimeStamp.seconds ?: 0) * 1000 + (creationTimeStamp.nanos ?: 0) / 1_000_000 @@ -395,7 +430,8 @@ suspend fun updateExpressAuthTokenWrapper(context: Context, expressIntegritySess private suspend fun regenerateToken( context: Context, authToken: String, packageName: String, clientKey: ClientKey ): AuthTokenWrapper { - try { + val prefs = context.getSharedPreferences("device_integrity_token", Context.MODE_PRIVATE) + val deviceIntegrityToken = try { Log.d(TAG, "regenerateToken authToken:$authToken, packageName:$packageName, clientKey:$clientKey") val droidGuardSessionTokenResponse = requestDroidGuardSessionToken(context, authToken) @@ -425,20 +461,22 @@ private suspend fun regenerateToken( val deviceIntegrityTokenType = deviceIntegrityTokenResponse.tokenWrapper?.tokenContent?.tokenType?.firstOrNull { it.type?.toInt() == 5 } ?: throw RuntimeException("regenerateToken deviceIntegrityTokenType is null!") - val deviceIntegrityToken = deviceIntegrityTokenType.tokenSessionWrapper?.wrapper?.sessionContent?.tokenContent?.tokenWrapper?.token - - return AuthTokenWrapper.Builder().apply { - this.clientKey = clientKey - this.deviceIntegrityWrapper = DeviceIntegrityWrapper.Builder().apply { - this.deviceIntegrityToken = deviceIntegrityToken ?: ByteString.EMPTY - this.creationTime = makeTimestamp(System.currentTimeMillis()) - }.build() - this.lastManualSoftRefreshTime = makeTimestamp(System.currentTimeMillis()) - }.build() + deviceIntegrityTokenType.tokenSessionWrapper?.wrapper?.sessionContent?.tokenContent?.tokenWrapper?.token?.also { + prefs.edit { putString(packageName, it.base64()) } + } } catch (e: Exception) { Log.d(TAG, "regenerateToken: error ", e) - return AuthTokenWrapper() + prefs.getString(packageName, null)?.decodeBase64() } + + return AuthTokenWrapper.Builder().apply { + this.clientKey = clientKey + this.deviceIntegrityWrapper = DeviceIntegrityWrapper.Builder().apply { + this.deviceIntegrityToken = deviceIntegrityToken ?: ByteString.EMPTY + this.creationTime = makeTimestamp(System.currentTimeMillis()) + }.build() + this.lastManualSoftRefreshTime = makeTimestamp(System.currentTimeMillis()) + }.build() } private suspend fun requestDroidGuardSessionToken(context: Context, authToken: String): TokenResponse { diff --git a/vending-app/src/main/kotlin/com/google/android/finsky/expressintegrityservice/ExpressIntegrityService.kt b/vending-app/src/main/kotlin/com/google/android/finsky/expressintegrityservice/ExpressIntegrityService.kt index 524e389f5e..ecaaf545dc 100644 --- a/vending-app/src/main/kotlin/com/google/android/finsky/expressintegrityservice/ExpressIntegrityService.kt +++ b/vending-app/src/main/kotlin/com/google/android/finsky/expressintegrityservice/ExpressIntegrityService.kt @@ -20,23 +20,22 @@ import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleService import androidx.lifecycle.lifecycleScope import com.android.vending.AUTH_TOKEN_SCOPE +import com.android.vending.VendingPreferences import com.android.vending.makeTimestamp import com.google.android.finsky.AuthTokenWrapper import com.google.android.finsky.ClientKey -import com.google.android.finsky.ClientKeyExtend import com.google.android.finsky.DeviceIntegrityWrapper import com.google.android.finsky.ExpressIntegrityResponse -import com.google.android.finsky.IntegrityAdvice import com.google.android.finsky.INTERMEDIATE_INTEGRITY_HARD_EXPIRATION +import com.google.android.finsky.IntegrityAdvice import com.google.android.finsky.IntermediateIntegrityRequest import com.google.android.finsky.IntermediateIntegrityResponse import com.google.android.finsky.IntermediateIntegritySession import com.google.android.finsky.KEY_CLOUD_PROJECT +import com.google.android.finsky.KEY_ERROR import com.google.android.finsky.KEY_NONCE -import com.google.android.finsky.KEY_OPT_PACKAGE import com.google.android.finsky.KEY_PACKAGE_NAME import com.google.android.finsky.KEY_REQUEST_MODE -import com.google.android.finsky.KEY_ERROR import com.google.android.finsky.KEY_REQUEST_TOKEN_SID import com.google.android.finsky.KEY_REQUEST_VERDICT_OPT_OUT import com.google.android.finsky.KEY_TOKEN @@ -48,13 +47,14 @@ import com.google.android.finsky.RequestMode import com.google.android.finsky.TestErrorType import com.google.android.finsky.buildClientKeyExtend import com.google.android.finsky.buildInstallSourceMetaData -import com.google.android.finsky.getPlayCoreVersion +import com.google.android.finsky.callerAppToVisitData import com.google.android.finsky.encodeBase64 import com.google.android.finsky.ensureContainsLockBootloader import com.google.android.finsky.getAuthToken import com.google.android.finsky.getExpirationTime import com.google.android.finsky.getIntegrityRequestWrapper import com.google.android.finsky.getPackageInfoCompat +import com.google.android.finsky.getPlayCoreVersion import com.google.android.finsky.isNetworkConnected import com.google.android.finsky.md5 import com.google.android.finsky.model.IntegrityErrorCode @@ -63,6 +63,7 @@ import com.google.android.finsky.readAes128GcmBuilderFromClientKey import com.google.android.finsky.requestIntermediateIntegrity import com.google.android.finsky.sha256 import com.google.android.finsky.signaturesCompat +import com.google.android.finsky.updateAppVisitContent import com.google.android.finsky.updateExpressAuthTokenWrapper import com.google.android.finsky.updateExpressClientKey import com.google.android.finsky.updateExpressSessionTime @@ -74,6 +75,7 @@ import com.google.android.play.core.integrity.protocol.IRequestDialogCallback import com.google.crypto.tink.config.TinkConfig import okio.ByteString.Companion.toByteString import org.microg.gms.profile.ProfileManager +import org.microg.gms.vending.IntegrityVisitData import org.microg.vending.billing.DEFAULT_ACCOUNT_TYPE import org.microg.vending.proto.Timestamp import kotlin.random.Random @@ -98,15 +100,29 @@ class ExpressIntegrityService : LifecycleService() { private class ExpressIntegrityServiceImpl(private val context: Context, override val lifecycle: Lifecycle) : IExpressIntegrityService.Stub(), LifecycleOwner { + private var visitData: IntegrityVisitData? = null + override fun warmUpIntegrityToken(bundle: Bundle, callback: IExpressIntegrityServiceCallback?) { lifecycleScope.launchWhenCreated { runCatching { + val playIntegrityEnabled = VendingPreferences.isPlayIntegrityEnabled(context) + if (!playIntegrityEnabled) { + throw StandardIntegrityException(IntegrityErrorCode.API_NOT_AVAILABLE, "API is disabled") + } + + val callingPackageName = bundle.getString(KEY_PACKAGE_NAME) + visitData = callerAppToVisitData(context, callingPackageName) + + if (visitData?.allowed != true) { + throw StandardIntegrityException(IntegrityErrorCode.API_NOT_AVAILABLE, "Not allowed visit API") + } + if (!context.isNetworkConnected()) { throw StandardIntegrityException(IntegrityErrorCode.NETWORK_ERROR, "No network is available") } val expressIntegritySession = ExpressIntegritySession( - packageName = bundle.getString(KEY_PACKAGE_NAME) ?: "", + packageName = callingPackageName ?: "", cloudProjectNumber = bundle.getLong(KEY_CLOUD_PROJECT, 0L), sessionId = Random.nextLong(), null, @@ -234,10 +250,12 @@ private class ExpressIntegrityServiceImpl(private val context: Context, override updateLocalExpressFilePB(context, intermediateIntegrityResponseData) + visitData?.updateAppVisitContent(context, System.currentTimeMillis(), "$TAG visited success.") callback?.onWarmResult(bundleOf(KEY_WARM_UP_SID to expressIntegritySession.sessionId)) }.onFailure { val exception = it as? StandardIntegrityException ?: StandardIntegrityException(it.message) Log.w(TAG, "warm up has failed: code=${exception.code}, message=${exception.message}", exception) + visitData?.updateAppVisitContent(context, System.currentTimeMillis(), "$TAG visited failed. ${exception.message}") callback?.onWarmResult(bundleOf(KEY_ERROR to exception.code)) } } @@ -247,8 +265,19 @@ private class ExpressIntegrityServiceImpl(private val context: Context, override Log.d(TAG, "requestExpressIntegrityToken bundle:$bundle") lifecycleScope.launchWhenCreated { runCatching { + val playIntegrityEnabled = VendingPreferences.isPlayIntegrityEnabled(context) + if (!playIntegrityEnabled) { + throw StandardIntegrityException(IntegrityErrorCode.API_NOT_AVAILABLE, "API is disabled") + } + val callingPackageName = bundle.getString(KEY_PACKAGE_NAME) + visitData = callerAppToVisitData(context, callingPackageName) + + if (visitData?.allowed != true) { + throw StandardIntegrityException(IntegrityErrorCode.API_NOT_AVAILABLE, "Not allowed visit") + } + val expressIntegritySession = ExpressIntegritySession( - packageName = bundle.getString(KEY_PACKAGE_NAME) ?: "", + packageName = callingPackageName ?: "", cloudProjectNumber = bundle.getLong(KEY_CLOUD_PROJECT, 0L), sessionId = Random.nextLong(), requestHash = bundle.getString(KEY_NONCE), @@ -321,6 +350,7 @@ private class ExpressIntegrityServiceImpl(private val context: Context, override ) Log.d(TAG, "requestExpressIntegrityToken token: $token, sid: ${expressIntegritySession.sessionId}, mode: ${expressIntegritySession.webViewRequestMode}") + visitData?.updateAppVisitContent(context, System.currentTimeMillis(), "$TAG visited success.") callback?.onRequestResult( bundleOf( KEY_TOKEN to token, @@ -331,6 +361,7 @@ private class ExpressIntegrityServiceImpl(private val context: Context, override }.onFailure { val exception = it as? StandardIntegrityException ?: StandardIntegrityException(it.message) Log.w(TAG, "requesting token has failed: code=${exception.code}, message=${exception.message}", exception) + visitData?.updateAppVisitContent(context, System.currentTimeMillis(), "$TAG visited failed. ${exception.message}") callback?.onRequestResult(bundleOf(KEY_ERROR to exception.code)) } } diff --git a/vending-app/src/main/kotlin/com/google/android/finsky/integrityservice/IntegrityService.kt b/vending-app/src/main/kotlin/com/google/android/finsky/integrityservice/IntegrityService.kt index 76478e7ade..f16cecb594 100644 --- a/vending-app/src/main/kotlin/com/google/android/finsky/integrityservice/IntegrityService.kt +++ b/vending-app/src/main/kotlin/com/google/android/finsky/integrityservice/IntegrityService.kt @@ -19,6 +19,7 @@ import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleService import androidx.lifecycle.lifecycleScope import com.android.vending.AUTH_TOKEN_SCOPE +import com.android.vending.VendingPreferences import com.android.vending.makeTimestamp import com.google.android.finsky.AccessibilityAbuseSignalDataWrapper import com.google.android.finsky.AppAccessRiskDetailsResponse @@ -44,14 +45,17 @@ import com.google.android.finsky.SIGNING_FLAGS import com.google.android.finsky.ScreenCaptureSignalDataWrapper import com.google.android.finsky.ScreenOverlaySignalDataWrapper import com.google.android.finsky.VersionCodeWrapper +import com.google.android.finsky.callerAppToVisitData import com.google.android.finsky.getPlayCoreVersion import com.google.android.finsky.encodeBase64 import com.google.android.finsky.getAuthToken import com.google.android.finsky.getPackageInfoCompat import com.google.android.finsky.model.IntegrityErrorCode +import com.google.android.finsky.model.StandardIntegrityException import com.google.android.finsky.requestIntegritySyncData import com.google.android.finsky.sha256 import com.google.android.finsky.signaturesCompat +import com.google.android.finsky.updateAppVisitContent import com.google.android.gms.droidguard.DroidGuard import com.google.android.gms.droidguard.internal.DroidGuardResultsRequest import com.google.android.gms.tasks.await @@ -62,6 +66,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import okio.ByteString.Companion.toByteString import org.microg.gms.profile.ProfileManager +import org.microg.gms.vending.IntegrityVisitData private const val TAG = "IntegrityService" @@ -82,6 +87,8 @@ class IntegrityService : LifecycleService() { private class IntegrityServiceImpl(private val context: Context, override val lifecycle: Lifecycle) : IIntegrityService.Stub(), LifecycleOwner { + private var visitData: IntegrityVisitData? = null + override fun requestDialog(bundle: Bundle, callback: IRequestDialogCallback) { Log.d(TAG, "Method (requestDialog) called but not implemented ") requestAndShowDialog(bundle, callback) @@ -93,63 +100,66 @@ private class IntegrityServiceImpl(private val context: Context, override val li override fun requestIntegrityToken(request: Bundle, callback: IIntegrityServiceCallback) { Log.d(TAG, "Method (requestIntegrityToken) called") - val packageName = request.getString(KEY_PACKAGE_NAME) - if (packageName == null) { - callback.onError("", IntegrityErrorCode.INTERNAL_ERROR, "Null packageName.") - return - } - val nonceArr = request.getByteArray(KEY_NONCE) - if (nonceArr == null) { - callback.onError(packageName, IntegrityErrorCode.INTERNAL_ERROR, "Nonce missing.") - return - } - if (nonceArr.size < 16) { - callback.onError(packageName, IntegrityErrorCode.NONCE_TOO_SHORT, "Nonce too short.") - return - } - if (nonceArr.size >= 500) { - callback.onError(packageName, IntegrityErrorCode.NONCE_TOO_LONG, "Nonce too long.") - return - } - val cloudProjectNumber = request.getLong(KEY_CLOUD_PROJECT, 0L) - val playCoreVersion = request.getPlayCoreVersion() - Log.d(TAG, "requestIntegrityToken(packageName: $packageName, nonce: ${nonceArr.encodeBase64(false)}, cloudProjectNumber: $cloudProjectNumber, playCoreVersion: $playCoreVersion)") - - val packageInfo = context.packageManager.getPackageInfoCompat(packageName, SIGNING_FLAGS) - val timestamp = makeTimestamp(System.currentTimeMillis()) - val versionCode = packageInfo.versionCode - - val integrityParams = IntegrityParams( - packageName = PackageNameWrapper(packageName), - versionCode = VersionCodeWrapper(versionCode), - nonce = nonceArr.encodeBase64(noPadding = false, noWrap = true, urlSafe = true), - certificateSha256Digests = packageInfo.signaturesCompat.map { - it.toByteArray().sha256().encodeBase64(noPadding = true, noWrap = true, urlSafe = true) - }, - timestampAtRequest = timestamp, - cloudProjectNumber = cloudProjectNumber.takeIf { it > 0L } - ) - - val data = mutableMapOf( - PARAMS_PKG_KEY to packageName, - PARAMS_VC_KEY to versionCode.toString(), - PARAMS_NONCE_SHA256_KEY to nonceArr.sha256().encodeBase64(noPadding = true, noWrap = true, urlSafe = true), - PARAMS_TM_S_KEY to timestamp.seconds.toString(), - PARAMS_BINDING_KEY to integrityParams.encode().encodeBase64(noPadding = false, noWrap = true, urlSafe = true), - ) - if (cloudProjectNumber > 0L) { - data[PARAMS_GCP_N_KEY] = cloudProjectNumber.toString() - } - - var mapSize = 0 - data.entries.forEach { mapSize += it.key.toByteArray().size + it.value.toByteArray().size } - if (mapSize > 65536) { - callback.onError(packageName, IntegrityErrorCode.INTERNAL_ERROR, "Content binding size exceeded maximum allowed size.") - return - } - lifecycleScope.launchWhenCreated { runCatching { + val playIntegrityEnabled = VendingPreferences.isPlayIntegrityEnabled(context) + if (!playIntegrityEnabled) { + throw StandardIntegrityException(IntegrityErrorCode.INTERNAL_ERROR, "API is disabled.") + } + val packageName = request.getString(KEY_PACKAGE_NAME) + if (packageName == null) { + throw StandardIntegrityException(IntegrityErrorCode.INTERNAL_ERROR, "Null packageName.") + } + visitData = callerAppToVisitData(context, packageName) + if (visitData?.allowed != true) { + throw StandardIntegrityException(IntegrityErrorCode.INTERNAL_ERROR, "Not allowed visit API.") + } + val nonceArr = request.getByteArray(KEY_NONCE) + if (nonceArr == null) { + throw StandardIntegrityException(IntegrityErrorCode.INTERNAL_ERROR, "Nonce missing.") + } + if (nonceArr.size < 16) { + throw StandardIntegrityException(IntegrityErrorCode.NONCE_TOO_SHORT, "Nonce too short.") + } + if (nonceArr.size >= 500) { + throw StandardIntegrityException(IntegrityErrorCode.NONCE_TOO_LONG, "Nonce too long.") + } + val cloudProjectNumber = request.getLong(KEY_CLOUD_PROJECT, 0L) + val playCoreVersion = request.getPlayCoreVersion() + Log.d(TAG, "requestIntegrityToken(packageName: $packageName, nonce: ${nonceArr.encodeBase64(false)}, cloudProjectNumber: $cloudProjectNumber, playCoreVersion: $playCoreVersion)") + + val packageInfo = context.packageManager.getPackageInfoCompat(packageName, SIGNING_FLAGS) + val timestamp = makeTimestamp(System.currentTimeMillis()) + val versionCode = packageInfo.versionCode + + val integrityParams = IntegrityParams( + packageName = PackageNameWrapper(packageName), + versionCode = VersionCodeWrapper(versionCode), + nonce = nonceArr.encodeBase64(noPadding = false, noWrap = true, urlSafe = true), + certificateSha256Digests = packageInfo.signaturesCompat.map { + it.toByteArray().sha256().encodeBase64(noPadding = true, noWrap = true, urlSafe = true) + }, + timestampAtRequest = timestamp, + cloudProjectNumber = cloudProjectNumber.takeIf { it > 0L } + ) + + val data = mutableMapOf( + PARAMS_PKG_KEY to packageName, + PARAMS_VC_KEY to versionCode.toString(), + PARAMS_NONCE_SHA256_KEY to nonceArr.sha256().encodeBase64(noPadding = true, noWrap = true, urlSafe = true), + PARAMS_TM_S_KEY to timestamp.seconds.toString(), + PARAMS_BINDING_KEY to integrityParams.encode().encodeBase64(noPadding = false, noWrap = true, urlSafe = true), + ) + if (cloudProjectNumber > 0L) { + data[PARAMS_GCP_N_KEY] = cloudProjectNumber.toString() + } + + var mapSize = 0 + data.entries.forEach { mapSize += it.key.toByteArray().size + it.value.toByteArray().size } + if (mapSize > 65536) { + throw StandardIntegrityException(IntegrityErrorCode.INTERNAL_ERROR, "Content binding size exceeded maximum allowed size.") + } + val authToken = getAuthToken(context, AUTH_TOKEN_SCOPE) if (TextUtils.isEmpty(authToken)) { Log.w(TAG, "requestIntegrityToken: Got null auth token for type: $AUTH_TOKEN_SCOPE") @@ -167,8 +177,7 @@ private class IntegrityServiceImpl(private val context: Context, override val li if (droidGuardData.utf8().startsWith(INTEGRITY_PREFIX_ERROR)) { Log.w(TAG, "droidGuardData: ${droidGuardData.utf8()}") - callback.onError(packageName, IntegrityErrorCode.NETWORK_ERROR, "DroidGuard failed.") - return@launchWhenCreated + throw StandardIntegrityException(IntegrityErrorCode.NETWORK_ERROR, "DroidGuard failed.") } val integrityRequest = IntegrityRequest( @@ -193,15 +202,16 @@ private class IntegrityServiceImpl(private val context: Context, override val li val integrityToken = integrityResponse.contentWrapper?.content?.token if (integrityToken.isNullOrEmpty()) { - callback.onError(packageName, IntegrityErrorCode.INTERNAL_ERROR, "IntegrityResponse didn't have a token") - return@launchWhenCreated + throw StandardIntegrityException(IntegrityErrorCode.INTERNAL_ERROR,"IntegrityResponse didn't have a token") } Log.d(TAG, "requestIntegrityToken integrityToken: $integrityToken") + visitData?.updateAppVisitContent(context, System.currentTimeMillis(), "$TAG visited success.") callback.onSuccess(packageName, integrityToken) }.onFailure { Log.w(TAG, "requestIntegrityToken has exception: ", it) - callback.onError(packageName, IntegrityErrorCode.INTERNAL_ERROR, it.message ?: "Exception") + visitData?.updateAppVisitContent(context, System.currentTimeMillis(), "$TAG visited failed. ${it.message}") + callback.onError(visitData?.packageName, IntegrityErrorCode.INTERNAL_ERROR, it.message ?: "Exception") } } } From e4406aafa2543dcc0f69ef7da0f49f094ca1368a Mon Sep 17 00:00:00 2001 From: Davinci9196 Date: Wed, 29 Oct 2025 14:10:03 +0800 Subject: [PATCH 2/3] Merged with safetyNet --- .../microg/gms/settings/SettingsContract.kt | 2 - .../microg/gms/settings/SettingsProvider.kt | 2 - ...grityVisitData.kt => PlayIntegrityData.kt} | 38 +++++---- .../gms/ui/PlayIntegrityManageFragment.kt | 85 ------------------- .../microg/gms/ui/SafetyNetAllAppsFragment.kt | 6 +- .../org/microg/gms/ui/SafetyNetAppFragment.kt | 47 +++++++++- .../org/microg/gms/ui/SafetyNetFragment.kt | 6 +- .../org/microg/gms/ui/VendingFragment.kt | 6 -- .../microg/gms/vending/VendingPreferences.kt | 15 ---- .../src/main/res/navigation/nav_settings.xml | 9 -- .../src/main/res/values-zh-rCN/strings.xml | 13 +-- .../src/main/res/values-zh-rTW/strings.xml | 6 +- .../src/main/res/values/strings.xml | 11 +-- .../res/xml/preferences_play_integrity.xml | 28 ------ .../res/xml/preferences_safetynet_app.xml | 9 ++ .../com/android/vending/VendingPreferences.kt | 5 +- .../android/finsky/IntegrityExtensions.kt | 22 +++-- .../ExpressIntegrityService.kt | 37 ++++---- .../integrityservice/IntegrityService.kt | 14 +-- 19 files changed, 135 insertions(+), 226 deletions(-) rename play-services-base/core/src/main/kotlin/org/microg/gms/vending/{IntegrityVisitData.kt => PlayIntegrityData.kt} (55%) delete mode 100644 play-services-core/src/main/kotlin/org/microg/gms/ui/PlayIntegrityManageFragment.kt delete mode 100644 play-services-core/src/main/res/xml/preferences_play_integrity.xml diff --git a/play-services-base/core/src/main/kotlin/org/microg/gms/settings/SettingsContract.kt b/play-services-base/core/src/main/kotlin/org/microg/gms/settings/SettingsContract.kt index fb5d072031..5ef726412c 100644 --- a/play-services-base/core/src/main/kotlin/org/microg/gms/settings/SettingsContract.kt +++ b/play-services-base/core/src/main/kotlin/org/microg/gms/settings/SettingsContract.kt @@ -279,7 +279,6 @@ object SettingsContract { const val ASSET_DEVICE_SYNC = "vending_device_sync" const val APPS_INSTALL = "vending_apps_install" const val APPS_INSTALLER_LIST = "vending_apps_installer_list" - const val PLAY_INTEGRITY = "vending_play_integrity" const val PLAY_INTEGRITY_APP_LIST = "vending_play_integrity_apps" val PROJECTION = arrayOf( @@ -291,7 +290,6 @@ object SettingsContract { ASSET_DEVICE_SYNC, APPS_INSTALL, APPS_INSTALLER_LIST, - PLAY_INTEGRITY, PLAY_INTEGRITY_APP_LIST ) } diff --git a/play-services-base/core/src/main/kotlin/org/microg/gms/settings/SettingsProvider.kt b/play-services-base/core/src/main/kotlin/org/microg/gms/settings/SettingsProvider.kt index 76946282de..df0cabfd41 100644 --- a/play-services-base/core/src/main/kotlin/org/microg/gms/settings/SettingsProvider.kt +++ b/play-services-base/core/src/main/kotlin/org/microg/gms/settings/SettingsProvider.kt @@ -369,7 +369,6 @@ class SettingsProvider : ContentProvider() { Vending.SPLIT_INSTALL -> getSettingsBoolean(key, false) Vending.APPS_INSTALL -> getSettingsBoolean(key, false) Vending.APPS_INSTALLER_LIST -> getSettingsString(key, "") - Vending.PLAY_INTEGRITY -> getSettingsBoolean(key, false) Vending.PLAY_INTEGRITY_APP_LIST -> getSettingsString(key, "") else -> throw IllegalArgumentException("Unknown key: $key") } @@ -388,7 +387,6 @@ class SettingsProvider : ContentProvider() { Vending.ASSET_DEVICE_SYNC -> editor.putBoolean(key, value as Boolean) Vending.APPS_INSTALL -> editor.putBoolean(key, value as Boolean) Vending.APPS_INSTALLER_LIST -> editor.putString(key, value as String) - Vending.PLAY_INTEGRITY -> editor.putBoolean(key, value as Boolean) Vending.PLAY_INTEGRITY_APP_LIST -> editor.putString(key, value as String) else -> throw IllegalArgumentException("Unknown key: $key") } diff --git a/play-services-base/core/src/main/kotlin/org/microg/gms/vending/IntegrityVisitData.kt b/play-services-base/core/src/main/kotlin/org/microg/gms/vending/PlayIntegrityData.kt similarity index 55% rename from play-services-base/core/src/main/kotlin/org/microg/gms/vending/IntegrityVisitData.kt rename to play-services-base/core/src/main/kotlin/org/microg/gms/vending/PlayIntegrityData.kt index c537a7692f..bb5fde337c 100644 --- a/play-services-base/core/src/main/kotlin/org/microg/gms/vending/IntegrityVisitData.kt +++ b/play-services-base/core/src/main/kotlin/org/microg/gms/vending/PlayIntegrityData.kt @@ -8,17 +8,21 @@ package org.microg.gms.vending import org.json.JSONException import org.json.JSONObject -class IntegrityVisitData(var allowed: Boolean, val packageName: String, val pkgSignSha256: String) { - var lastVisitTime: Long? = null - var lastVisitResult: String? = null +class PlayIntegrityData(var allowed: Boolean, + val packageName: String, + val pkgSignSha256: String, + var lastTime: Long, + var lastResult: String? = null, + var lastStatus: Boolean = false) { override fun toString(): String { return JSONObject() .put(ALLOWED, allowed) .put(PACKAGE_NAME, packageName) .put(SIGNATURE, pkgSignSha256) - .put(LAST_VISIT_TIME, lastVisitTime) - .put(LAST_VISIT_RESULT, lastVisitResult) + .put(LAST_VISIT_TIME, lastTime) + .put(LAST_VISIT_RESULT, lastResult) + .put(LAST_VISIT_STATUS, lastStatus) .toString() } @@ -28,33 +32,35 @@ class IntegrityVisitData(var allowed: Boolean, val packageName: String, val pkgS private const val SIGNATURE = "signature" private const val LAST_VISIT_TIME = "lastVisitTime" private const val LAST_VISIT_RESULT = "lastVisitResult" + private const val LAST_VISIT_STATUS = "lastVisitStatus" - private fun parse(jsonString: String): IntegrityVisitData? { + private fun parse(jsonString: String): PlayIntegrityData? { try { val json = JSONObject(jsonString) - return IntegrityVisitData( + return PlayIntegrityData( json.getBoolean(ALLOWED), json.getString(PACKAGE_NAME), - json.getString(SIGNATURE) - ).apply { - lastVisitTime = json.getLong(LAST_VISIT_TIME) - lastVisitResult = json.getString(LAST_VISIT_RESULT) - } + json.getString(SIGNATURE), + json.getLong(LAST_VISIT_TIME), + json.getString(LAST_VISIT_RESULT), + json.getBoolean(LAST_VISIT_STATUS) + ) } catch (e: JSONException) { return null } } - fun loadDataSet(content: String): Set { + fun loadDataSet(content: String): Set { return content.split("|").mapNotNull { parse(it) }.toSet() } - fun updateDataSetString(channelList: Set, channel: IntegrityVisitData): String { + fun updateDataSetString(channelList: Set, channel: PlayIntegrityData): String { val channelData = channelList.find { it.packageName == channel.packageName && it.pkgSignSha256 == channel.pkgSignSha256 } val newChannelList = if (channelData != null) { channelData.allowed = channel.allowed - channelData.lastVisitTime = channel.lastVisitTime - channelData.lastVisitResult = channel.lastVisitResult + channelData.lastTime = channel.lastTime + channelData.lastResult = channel.lastResult + channelData.lastStatus = channel.lastStatus channelList } else { channelList + channel diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/PlayIntegrityManageFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/PlayIntegrityManageFragment.kt deleted file mode 100644 index 56eea118f0..0000000000 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/PlayIntegrityManageFragment.kt +++ /dev/null @@ -1,85 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2025 microG Project Team - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.microg.gms.ui - -import android.os.Bundle -import androidx.lifecycle.lifecycleScope -import androidx.preference.Preference -import androidx.preference.PreferenceCategory -import androidx.preference.PreferenceFragmentCompat -import androidx.preference.SwitchPreference -import com.google.android.gms.R -import org.microg.gms.utils.getApplicationLabel -import org.microg.gms.vending.IntegrityVisitData -import org.microg.gms.vending.VendingPreferences -import java.text.SimpleDateFormat -import java.util.Date - -class PlayIntegrityManageFragment : PreferenceFragmentCompat() { - private lateinit var switchBarPreference: SwitchBarPreference - private lateinit var apps: PreferenceCategory - private lateinit var appsNone: Preference - - override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { - addPreferencesFromResource(R.xml.preferences_play_integrity) - - switchBarPreference = preferenceScreen.findPreference("pref_play_integrity_enabled") ?: switchBarPreference - apps = preferenceScreen.findPreference("pref_play_integrity_apps") ?: apps - appsNone = preferenceScreen.findPreference("pref_play_integrity_apps_none") ?: appsNone - - switchBarPreference.setOnPreferenceChangeListener { _, newValue -> - val appContext = requireContext().applicationContext - lifecycleScope.launchWhenResumed { - if (newValue is Boolean) { - VendingPreferences.setPlayIntegrityEnabled(appContext, newValue) - } - updateContent() - } - true - } - } - - override fun onResume() { - super.onResume() - updateContent() - } - - fun updateContent() { - val appContext = requireContext().applicationContext - lifecycleScope.launchWhenResumed { - switchBarPreference.isChecked = VendingPreferences.isPlayIntegrityEnabled(appContext) - val visitAppContent = VendingPreferences.getPlayIntegrityAppList(appContext) - val visitAppSet = IntegrityVisitData.loadDataSet(visitAppContent) - val visitAppViews = visitAppSet.mapNotNull { - runCatching { - SwitchPreference(appContext).apply { - key = "pref_permission_apps_${it.packageName}" - title = appContext.packageManager.getApplicationLabel(it.packageName) - icon = appContext.packageManager.getApplicationIcon(it.packageName) - isChecked = it.allowed - summary = "Last: ${it.lastVisitResult} \n${it.lastVisitTime?.let { time -> SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(Date(time)) }}" - setOnPreferenceChangeListener { _, newValue -> - lifecycleScope.launchWhenResumed { - if (newValue is Boolean) { - val content = IntegrityVisitData.updateDataSetString(visitAppSet, it.apply { this.allowed = newValue }) - VendingPreferences.setPlayIntegrityAppList(appContext, content) - } - } - true - } - } - }.getOrNull() - } - apps.removeAll() - for (visitAppView in visitAppViews) { - apps.addPreference(visitAppView) - } - if (visitAppViews.isEmpty()) { - apps.addPreference(appsNone) - } - } - } -} \ No newline at end of file diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/SafetyNetAllAppsFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/SafetyNetAllAppsFragment.kt index 78a489d040..cf06c39289 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/SafetyNetAllAppsFragment.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/SafetyNetAllAppsFragment.kt @@ -18,6 +18,8 @@ import com.google.android.gms.R import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.microg.gms.safetynet.SafetyNetDatabase +import org.microg.gms.vending.PlayIntegrityData +import org.microg.gms.vending.VendingPreferences class SafetyNetAllAppsFragment : PreferenceFragmentCompat() { private lateinit var database: SafetyNetDatabase @@ -50,8 +52,10 @@ class SafetyNetAllAppsFragment : PreferenceFragmentCompat() { private fun updateContent() { val context = requireContext() lifecycleScope.launchWhenResumed { + val playIntegrityData = VendingPreferences.getPlayIntegrityAppList(context) val apps = withContext(Dispatchers.IO) { - val res = database.recentApps.map { app -> + val playPairs = PlayIntegrityData.loadDataSet(playIntegrityData).map { it.packageName to it.lastTime } + val res = (database.recentApps + playPairs).map { app -> val pref = AppIconPreference(context) pref.packageName = app.first pref.summary = when { diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/SafetyNetAppFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/SafetyNetAppFragment.kt index 6e3cb295c1..c3cf395a4e 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/SafetyNetAppFragment.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/SafetyNetAppFragment.kt @@ -8,16 +8,26 @@ package org.microg.gms.ui import android.annotation.SuppressLint import android.os.Bundle import android.text.format.DateUtils +import androidx.core.content.ContextCompat import androidx.lifecycle.lifecycleScope -import androidx.preference.* +import androidx.preference.Preference +import androidx.preference.PreferenceCategory +import androidx.preference.PreferenceFragmentCompat +import androidx.preference.SwitchPreferenceCompat +import androidx.preference.isEmpty import com.google.android.gms.R import org.microg.gms.safetynet.SafetyNetDatabase -import org.microg.gms.safetynet.SafetyNetRequestType.* +import org.microg.gms.safetynet.SafetyNetRequestType.ATTESTATION +import org.microg.gms.safetynet.SafetyNetRequestType.RECAPTCHA +import org.microg.gms.safetynet.SafetyNetRequestType.RECAPTCHA_ENTERPRISE +import org.microg.gms.vending.PlayIntegrityData +import org.microg.gms.vending.VendingPreferences class SafetyNetAppFragment : PreferenceFragmentCompat() { private lateinit var appHeadingPreference: AppHeadingPreference private lateinit var recents: PreferenceCategory private lateinit var recentsNone: Preference + private lateinit var recentRequestAllow: SwitchPreferenceCompat private val packageName: String? get() = arguments?.getString("package") @@ -30,6 +40,16 @@ class SafetyNetAppFragment : PreferenceFragmentCompat() { appHeadingPreference = preferenceScreen.findPreference("pref_safetynet_app_heading") ?: appHeadingPreference recents = preferenceScreen.findPreference("prefcat_safetynet_recent_list") ?: recents recentsNone = preferenceScreen.findPreference("pref_safetynet_recent_none") ?: recentsNone + recentRequestAllow = preferenceScreen.findPreference("pref_safetynet_app_allow_request") ?: recentRequestAllow + recentRequestAllow.setOnPreferenceChangeListener { _, newValue -> + val playIntegrityDataSet = loadPlayIntegrityData() + val integrityData = packageName?.let { packageName -> playIntegrityDataSet.find { packageName == it.packageName } } + if (newValue is Boolean && integrityData != null) { + val content = PlayIntegrityData.updateDataSetString(playIntegrityDataSet, integrityData.apply { this.allowed = newValue }) + VendingPreferences.setPlayIntegrityAppList(requireContext(), content) + } + true + } } override fun onResume() { @@ -37,6 +57,11 @@ class SafetyNetAppFragment : PreferenceFragmentCompat() { updateContent() } + private fun loadPlayIntegrityData(): Set { + val playIntegrityData = VendingPreferences.getPlayIntegrityAppList(requireContext()) + return PlayIntegrityData.loadDataSet(playIntegrityData) + } + fun updateContent() { lifecycleScope.launchWhenResumed { appHeadingPreference.packageName = packageName @@ -52,7 +77,6 @@ class SafetyNetAppFragment : PreferenceFragmentCompat() { }.orEmpty() recents.removeAll() recents.addPreference(recentsNone) - recentsNone.isVisible = summaries.isEmpty() for (summary in summaries) { val preference = Preference(requireContext()) preference.onPreferenceClickListener = Preference.OnPreferenceClickListener { @@ -84,6 +108,23 @@ class SafetyNetAppFragment : PreferenceFragmentCompat() { } recents.addPreference(preference) } + val piContent = packageName?.let { packageName -> loadPlayIntegrityData().find { packageName == it.packageName } } + if (piContent != null) { + val preference = Preference(requireContext()) + val date = DateUtils.getRelativeDateTimeString( + context, + piContent.lastTime, + DateUtils.MINUTE_IN_MILLIS, + DateUtils.WEEK_IN_MILLIS, + DateUtils.FORMAT_SHOW_TIME + ) + preference.title = date + preference.summary = piContent.lastResult + preference.icon = if (piContent.lastStatus) ContextCompat.getDrawable(context, R.drawable.ic_circle_check) else ContextCompat.getDrawable(context, R.drawable.ic_circle_warn) + recents.addPreference(preference) + } + recentsNone.isVisible = recents.isEmpty() + recentRequestAllow.isVisible = piContent != null } } diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/SafetyNetFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/SafetyNetFragment.kt index e2a0090ddc..91f6e0b419 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/SafetyNetFragment.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/SafetyNetFragment.kt @@ -5,7 +5,6 @@ package org.microg.gms.ui -import android.annotation.SuppressLint import android.os.Bundle import android.util.Base64 import android.util.Log @@ -38,6 +37,8 @@ import org.microg.gms.safetynet.SafetyNetDatabase import org.microg.gms.safetynet.SafetyNetPreferences import org.microg.gms.safetynet.SafetyNetRequestType.* import org.microg.gms.utils.singleInstanceOf +import org.microg.gms.vending.PlayIntegrityData +import org.microg.gms.vending.VendingPreferences import java.net.URLEncoder import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException @@ -231,9 +232,10 @@ class SafetyNetFragment : PreferenceFragmentCompat() { lifecycleScope.launchWhenResumed { val context = requireContext() val (apps, showAll) = withContext(Dispatchers.IO) { + val playIntegrityData = VendingPreferences.getPlayIntegrityAppList(context) val db = SafetyNetDatabase(context) val apps = try { - db.recentApps + db.recentApps + PlayIntegrityData.loadDataSet(playIntegrityData).map { it.packageName to it.lastTime } } finally { db.close() } diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/VendingFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/VendingFragment.kt index cb2d2f2bbd..aca26dd982 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/VendingFragment.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/VendingFragment.kt @@ -128,7 +128,6 @@ class VendingFragment : PreferenceFragmentCompat() { override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { menu.add(0, MENU_INSTALL_MANAGED, 0, R.string.pref_app_install_settings_title) - menu.add(0, MENU_PI_MANAGE, 0, R.string.menu_play_integrity_manage) super.onCreateOptionsMenu(menu, inflater) } @@ -138,16 +137,11 @@ class VendingFragment : PreferenceFragmentCompat() { findNavController().navigate(requireContext(), R.id.openVendingInstallSettings) true } - MENU_PI_MANAGE -> { - findNavController().navigate(requireContext(), R.id.openPiManageFragment) - true - } else -> super.onOptionsItemSelected(item) } } companion object { private const val MENU_INSTALL_MANAGED = Menu.FIRST - private const val MENU_PI_MANAGE = Menu.FIRST + 1 const val PREF_LICENSING_ENABLED = "vending_licensing" const val PREF_LICENSING_PURCHASE_FREE_APPS_ENABLED = "vending_licensing_purchase_free_apps" const val PREF_SPLIT_INSTALL_ENABLED = "vending_split_install" diff --git a/play-services-core/src/main/kotlin/org/microg/gms/vending/VendingPreferences.kt b/play-services-core/src/main/kotlin/org/microg/gms/vending/VendingPreferences.kt index f0ec443550..59de550ea8 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/vending/VendingPreferences.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/vending/VendingPreferences.kt @@ -130,21 +130,6 @@ object VendingPreferences { } } - @JvmStatic - fun isPlayIntegrityEnabled(context: Context): Boolean { - val projection = arrayOf(SettingsContract.Vending.PLAY_INTEGRITY) - return SettingsContract.getSettings(context, SettingsContract.Vending.getContentUri(context), projection) { c -> - c.getInt(0) != 0 - } - } - - @JvmStatic - fun setPlayIntegrityEnabled(context: Context, enabled: Boolean) { - SettingsContract.setSettings(context, SettingsContract.Vending.getContentUri(context)) { - put(SettingsContract.Vending.PLAY_INTEGRITY, enabled) - } - } - @JvmStatic fun getPlayIntegrityAppList(context: Context): String { val projection = arrayOf(SettingsContract.Vending.PLAY_INTEGRITY_APP_LIST) diff --git a/play-services-core/src/main/res/navigation/nav_settings.xml b/play-services-core/src/main/res/navigation/nav_settings.xml index 65613175ca..4e202752d1 100644 --- a/play-services-core/src/main/res/navigation/nav_settings.xml +++ b/play-services-core/src/main/res/navigation/nav_settings.xml @@ -172,15 +172,6 @@ - - - - diff --git a/play-services-core/src/main/res/values-zh-rCN/strings.xml b/play-services-core/src/main/res/values-zh-rCN/strings.xml index e94899c5d8..8e5c5417fd 100644 --- a/play-services-core/src/main/res/values-zh-rCN/strings.xml +++ b/play-services-core/src/main/res/values-zh-rCN/strings.xml @@ -136,6 +136,8 @@ 自定义:%s 自动:%s 系统:%s + 允许请求 + 允许应用程序请求设备身份验证 "测试 SafetyNet 认证" "Google SafetyNet 是一套设备认证系统,旨在确认设备具有适当安全性,并与 Android CTS 兼容。某些应用会出于安全考虑或是防篡改目的而使用 SafetyNet。 @@ -153,16 +155,9 @@ microG GmsCore 内置一套自由的 SafetyNet 实现,但是官方服务器要 从文件导入自定义的设备配置信息 选择配置信息 设备配置信息 - 使用 SafetyNet 的应用 + 使用设备认证的应用 清除近期的 SafetyNet 请求 最近使用于%1$s - Integrity API 管理 - Google Play Integrity - 允许 API 认证 - 访问 Play Integrity API 的应用 - "Play Integrity API 是 Google 用来替代 SafetyNet 的新一代设备与应用完整性验证服务。某些应用会出于安全考虑或是防篡改目的而使用 Play Integrity API。 - -microG Companion 内置一套自由的 Play Integrity API 实现, 但是官方服务器存在校验返回数据安全的可能性。" 评估类型 响应状态 响应数据 @@ -232,7 +227,7 @@ microG Companion 内置一套自由的 Play Integrity API 实现, 但是官方 证明:%s 添加和管理 Google 账号 读取Google服务配置 - Google SafetyNet + Google 设备认证 ReCaptcha: %s ReCaptcha Enterprise: %s Google 游戏账号 diff --git a/play-services-core/src/main/res/values-zh-rTW/strings.xml b/play-services-core/src/main/res/values-zh-rTW/strings.xml index 2d1b451be5..cad516518e 100644 --- a/play-services-core/src/main/res/values-zh-rTW/strings.xml +++ b/play-services-core/src/main/res/values-zh-rTW/strings.xml @@ -159,7 +159,7 @@ 警告:%s 執行中… 運作模式 - 使用 SafetyNet 的應用程式 + 使用設備認證的應用程式 清除最近的請求 原生 實機 @@ -195,7 +195,9 @@ 存取您的車輛行駛里程 車用廠商通訊通道 存取您車輛的車廠專屬通道,以交換與車輛相關的專屬資訊 - Google SafetyNet + Google 設備認證 + 允許請求 + 允許應用程式請求裝置身份驗證 啟用此功能後,驗證請求中將不包含裝置名稱,這可能允許未授權的裝置登入,但也可能導致不可預期的後果。 狀態 更多 diff --git a/play-services-core/src/main/res/values/strings.xml b/play-services-core/src/main/res/values/strings.xml index b8fb1f008a..8d2fb3fea8 100644 --- a/play-services-core/src/main/res/values/strings.xml +++ b/play-services-core/src/main/res/values/strings.xml @@ -95,7 +95,7 @@ Please set up a password, PIN, or pattern lock screen." Google device registration Cloud Messaging - Google SafetyNet + Google Device Attestation Play Store services Work profile @@ -238,15 +238,12 @@ Please set up a password, PIN, or pattern lock screen." Running… Operation mode DroidGuard execution is unsupported on this device. SafetyNet services may misbehave. - Apps using SafetyNet + Apps using Device Attestation + Allow Request + Allow the app to request device authentication Clear recent requests Last use: %1$s - Integrity API Managed - Google Play Integrity - Allow API authentication - Apps using Play Integrity API - "The Play Integrity API is Google's next-generation device and app integrity verification service, replacing SafetyNet. Some apps use the Play Integrity API for security or tamper prevention purposes. \n\nmicroG Companion includes a free implementation of the Play Integrity API, but the official server may verify the security of the returned data." Native Real Custom: %s diff --git a/play-services-core/src/main/res/xml/preferences_play_integrity.xml b/play-services-core/src/main/res/xml/preferences_play_integrity.xml deleted file mode 100644 index b2f686e083..0000000000 --- a/play-services-core/src/main/res/xml/preferences_play_integrity.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - diff --git a/play-services-core/src/main/res/xml/preferences_safetynet_app.xml b/play-services-core/src/main/res/xml/preferences_safetynet_app.xml index c799e545f9..6ef57ab4c9 100644 --- a/play-services-core/src/main/res/xml/preferences_safetynet_app.xml +++ b/play-services-core/src/main/res/xml/preferences_safetynet_app.xml @@ -13,6 +13,15 @@ tools:title="@tools:sample/lorem" app:allowDividerBelow="false" /> + + + fun isDeviceAttestationEnabled(context: Context): Boolean { + return SettingsContract.getSettings(context, SettingsContract.SafetyNet.getContentUri(context), SettingsContract.SafetyNet.PROJECTION) { c -> c.getInt(0) != 0 } } diff --git a/vending-app/src/main/kotlin/com/google/android/finsky/IntegrityExtensions.kt b/vending-app/src/main/kotlin/com/google/android/finsky/IntegrityExtensions.kt index 720bcf0da6..0233fbf28d 100644 --- a/vending-app/src/main/kotlin/com/google/android/finsky/IntegrityExtensions.kt +++ b/vending-app/src/main/kotlin/com/google/android/finsky/IntegrityExtensions.kt @@ -47,7 +47,7 @@ import org.microg.gms.common.Constants import org.microg.gms.profile.Build import org.microg.gms.profile.ProfileManager import org.microg.gms.utils.getFirstSignatureDigest -import org.microg.gms.vending.IntegrityVisitData +import org.microg.gms.vending.PlayIntegrityData import org.microg.vending.billing.DEFAULT_ACCOUNT_TYPE import org.microg.vending.billing.GServices import org.microg.vending.billing.core.HttpClient @@ -104,10 +104,7 @@ private const val DEVICE_INTEGRITY_HARD_EXPIRATION = 432000L // 5 day const val INTERMEDIATE_INTEGRITY_HARD_EXPIRATION = 86400L // 1 day private const val TAG = "IntegrityExtensions" -fun callerAppToVisitData(context: Context, callingPackage: String?): IntegrityVisitData { - if (callingPackage.isNullOrEmpty()) { - throw StandardIntegrityException(IntegrityErrorCode.INTERNAL_ERROR, "Package name is empty") - } +fun callerAppToVisitData(context: Context, callingPackage: String): PlayIntegrityData { val pkgSignSha256ByteArray = context.packageManager.getFirstSignatureDigest(callingPackage, "SHA-256") if (pkgSignSha256ByteArray == null) { throw StandardIntegrityException(IntegrityErrorCode.APP_NOT_INSTALLED, "$callingPackage signature is null") @@ -115,19 +112,20 @@ fun callerAppToVisitData(context: Context, callingPackage: String?): IntegrityVi val pkgSignSha256 = Base64.encodeToString(pkgSignSha256ByteArray, Base64.NO_WRAP) Log.d(TAG, "callerToVisitData $callingPackage pkgSignSha256: $pkgSignSha256") val playIntegrityAppList = VendingPreferences.getPlayIntegrityAppList(context) - val loadDataSet = IntegrityVisitData.loadDataSet(playIntegrityAppList) + val loadDataSet = PlayIntegrityData.loadDataSet(playIntegrityAppList) if (loadDataSet.isEmpty() || loadDataSet.none { it.packageName == callingPackage && it.pkgSignSha256 == pkgSignSha256 }) { - return IntegrityVisitData(true, callingPackage, pkgSignSha256) + return PlayIntegrityData(true, callingPackage, pkgSignSha256, System.currentTimeMillis()) } return loadDataSet.first { it.packageName == callingPackage && it.pkgSignSha256 == pkgSignSha256 } } -fun IntegrityVisitData.updateAppVisitContent(context: Context, visitTime: Long, visitResult: String) { +fun PlayIntegrityData.updateAppVisitContent(context: Context, time: Long, result: String, status: Boolean = false) { val playIntegrityAppList = VendingPreferences.getPlayIntegrityAppList(context) - val loadDataSet = IntegrityVisitData.loadDataSet(playIntegrityAppList) - val dataSetString = IntegrityVisitData.updateDataSetString(loadDataSet, apply { - lastVisitTime = visitTime - lastVisitResult = visitResult + val loadDataSet = PlayIntegrityData.loadDataSet(playIntegrityAppList) + val dataSetString = PlayIntegrityData.updateDataSetString(loadDataSet, apply { + lastTime = time + lastResult = result + lastStatus = status }) VendingPreferences.setPlayIntegrityAppList(context, dataSetString) } diff --git a/vending-app/src/main/kotlin/com/google/android/finsky/expressintegrityservice/ExpressIntegrityService.kt b/vending-app/src/main/kotlin/com/google/android/finsky/expressintegrityservice/ExpressIntegrityService.kt index ecaaf545dc..7fd4e24c86 100644 --- a/vending-app/src/main/kotlin/com/google/android/finsky/expressintegrityservice/ExpressIntegrityService.kt +++ b/vending-app/src/main/kotlin/com/google/android/finsky/expressintegrityservice/ExpressIntegrityService.kt @@ -75,7 +75,7 @@ import com.google.android.play.core.integrity.protocol.IRequestDialogCallback import com.google.crypto.tink.config.TinkConfig import okio.ByteString.Companion.toByteString import org.microg.gms.profile.ProfileManager -import org.microg.gms.vending.IntegrityVisitData +import org.microg.gms.vending.PlayIntegrityData import org.microg.vending.billing.DEFAULT_ACCOUNT_TYPE import org.microg.vending.proto.Timestamp import kotlin.random.Random @@ -100,21 +100,22 @@ class ExpressIntegrityService : LifecycleService() { private class ExpressIntegrityServiceImpl(private val context: Context, override val lifecycle: Lifecycle) : IExpressIntegrityService.Stub(), LifecycleOwner { - private var visitData: IntegrityVisitData? = null + private var visitData: PlayIntegrityData? = null override fun warmUpIntegrityToken(bundle: Bundle, callback: IExpressIntegrityServiceCallback?) { lifecycleScope.launchWhenCreated { runCatching { - val playIntegrityEnabled = VendingPreferences.isPlayIntegrityEnabled(context) - if (!playIntegrityEnabled) { - throw StandardIntegrityException(IntegrityErrorCode.API_NOT_AVAILABLE, "API is disabled") - } - val callingPackageName = bundle.getString(KEY_PACKAGE_NAME) + if (callingPackageName == null) { + throw StandardIntegrityException(IntegrityErrorCode.INTERNAL_ERROR, "Null packageName.") + } visitData = callerAppToVisitData(context, callingPackageName) - if (visitData?.allowed != true) { - throw StandardIntegrityException(IntegrityErrorCode.API_NOT_AVAILABLE, "Not allowed visit API") + throw StandardIntegrityException(IntegrityErrorCode.API_NOT_AVAILABLE, "Not allowed visit") + } + val playIntegrityEnabled = VendingPreferences.isDeviceAttestationEnabled(context) + if (!playIntegrityEnabled) { + throw StandardIntegrityException(IntegrityErrorCode.API_NOT_AVAILABLE, "API is disabled") } if (!context.isNetworkConnected()) { @@ -250,7 +251,7 @@ private class ExpressIntegrityServiceImpl(private val context: Context, override updateLocalExpressFilePB(context, intermediateIntegrityResponseData) - visitData?.updateAppVisitContent(context, System.currentTimeMillis(), "$TAG visited success.") + visitData?.updateAppVisitContent(context, System.currentTimeMillis(), "$TAG visited success.", true) callback?.onWarmResult(bundleOf(KEY_WARM_UP_SID to expressIntegritySession.sessionId)) }.onFailure { val exception = it as? StandardIntegrityException ?: StandardIntegrityException(it.message) @@ -265,19 +266,21 @@ private class ExpressIntegrityServiceImpl(private val context: Context, override Log.d(TAG, "requestExpressIntegrityToken bundle:$bundle") lifecycleScope.launchWhenCreated { runCatching { - val playIntegrityEnabled = VendingPreferences.isPlayIntegrityEnabled(context) - if (!playIntegrityEnabled) { - throw StandardIntegrityException(IntegrityErrorCode.API_NOT_AVAILABLE, "API is disabled") - } val callingPackageName = bundle.getString(KEY_PACKAGE_NAME) + if (callingPackageName == null) { + throw StandardIntegrityException(IntegrityErrorCode.INTERNAL_ERROR, "Null packageName.") + } visitData = callerAppToVisitData(context, callingPackageName) - if (visitData?.allowed != true) { throw StandardIntegrityException(IntegrityErrorCode.API_NOT_AVAILABLE, "Not allowed visit") } + val playIntegrityEnabled = VendingPreferences.isDeviceAttestationEnabled(context) + if (!playIntegrityEnabled) { + throw StandardIntegrityException(IntegrityErrorCode.API_NOT_AVAILABLE, "API is disabled") + } val expressIntegritySession = ExpressIntegritySession( - packageName = callingPackageName ?: "", + packageName = callingPackageName, cloudProjectNumber = bundle.getLong(KEY_CLOUD_PROJECT, 0L), sessionId = Random.nextLong(), requestHash = bundle.getString(KEY_NONCE), @@ -350,7 +353,7 @@ private class ExpressIntegrityServiceImpl(private val context: Context, override ) Log.d(TAG, "requestExpressIntegrityToken token: $token, sid: ${expressIntegritySession.sessionId}, mode: ${expressIntegritySession.webViewRequestMode}") - visitData?.updateAppVisitContent(context, System.currentTimeMillis(), "$TAG visited success.") + visitData?.updateAppVisitContent(context, System.currentTimeMillis(), "$TAG visited success.", true) callback?.onRequestResult( bundleOf( KEY_TOKEN to token, diff --git a/vending-app/src/main/kotlin/com/google/android/finsky/integrityservice/IntegrityService.kt b/vending-app/src/main/kotlin/com/google/android/finsky/integrityservice/IntegrityService.kt index f16cecb594..1b5ce5fda9 100644 --- a/vending-app/src/main/kotlin/com/google/android/finsky/integrityservice/IntegrityService.kt +++ b/vending-app/src/main/kotlin/com/google/android/finsky/integrityservice/IntegrityService.kt @@ -66,7 +66,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import okio.ByteString.Companion.toByteString import org.microg.gms.profile.ProfileManager -import org.microg.gms.vending.IntegrityVisitData +import org.microg.gms.vending.PlayIntegrityData private const val TAG = "IntegrityService" @@ -87,7 +87,7 @@ class IntegrityService : LifecycleService() { private class IntegrityServiceImpl(private val context: Context, override val lifecycle: Lifecycle) : IIntegrityService.Stub(), LifecycleOwner { - private var visitData: IntegrityVisitData? = null + private var visitData: PlayIntegrityData? = null override fun requestDialog(bundle: Bundle, callback: IRequestDialogCallback) { Log.d(TAG, "Method (requestDialog) called but not implemented ") @@ -102,10 +102,6 @@ private class IntegrityServiceImpl(private val context: Context, override val li Log.d(TAG, "Method (requestIntegrityToken) called") lifecycleScope.launchWhenCreated { runCatching { - val playIntegrityEnabled = VendingPreferences.isPlayIntegrityEnabled(context) - if (!playIntegrityEnabled) { - throw StandardIntegrityException(IntegrityErrorCode.INTERNAL_ERROR, "API is disabled.") - } val packageName = request.getString(KEY_PACKAGE_NAME) if (packageName == null) { throw StandardIntegrityException(IntegrityErrorCode.INTERNAL_ERROR, "Null packageName.") @@ -114,6 +110,10 @@ private class IntegrityServiceImpl(private val context: Context, override val li if (visitData?.allowed != true) { throw StandardIntegrityException(IntegrityErrorCode.INTERNAL_ERROR, "Not allowed visit API.") } + val playIntegrityEnabled = VendingPreferences.isDeviceAttestationEnabled(context) + if (!playIntegrityEnabled) { + throw StandardIntegrityException(IntegrityErrorCode.INTERNAL_ERROR, "API is disabled.") + } val nonceArr = request.getByteArray(KEY_NONCE) if (nonceArr == null) { throw StandardIntegrityException(IntegrityErrorCode.INTERNAL_ERROR, "Nonce missing.") @@ -206,7 +206,7 @@ private class IntegrityServiceImpl(private val context: Context, override val li } Log.d(TAG, "requestIntegrityToken integrityToken: $integrityToken") - visitData?.updateAppVisitContent(context, System.currentTimeMillis(), "$TAG visited success.") + visitData?.updateAppVisitContent(context, System.currentTimeMillis(), "$TAG visited success.", true) callback.onSuccess(packageName, integrityToken) }.onFailure { Log.w(TAG, "requestIntegrityToken has exception: ", it) From 4bf6f52b243294feadb9515df54cb81016f2a907 Mon Sep 17 00:00:00 2001 From: Davinci9196 Date: Wed, 29 Oct 2025 14:15:46 +0800 Subject: [PATCH 3/3] cleanCode --- play-services-core/src/huawei/AndroidManifest.xml | 3 --- .../src/main/kotlin/org/microg/gms/ui/VendingFragment.kt | 1 + 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/play-services-core/src/huawei/AndroidManifest.xml b/play-services-core/src/huawei/AndroidManifest.xml index ce8aabcce8..26aa9013ea 100644 --- a/play-services-core/src/huawei/AndroidManifest.xml +++ b/play-services-core/src/huawei/AndroidManifest.xml @@ -58,8 +58,5 @@ - \ No newline at end of file diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/VendingFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/VendingFragment.kt index aca26dd982..acd224c188 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/VendingFragment.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/VendingFragment.kt @@ -137,6 +137,7 @@ class VendingFragment : PreferenceFragmentCompat() { findNavController().navigate(requireContext(), R.id.openVendingInstallSettings) true } + else -> super.onOptionsItemSelected(item) } }