From 0d25502c62ed749b3bfb0ac6fc84e35b4195be02 Mon Sep 17 00:00:00 2001 From: Davinci9196 Date: Thu, 28 Aug 2025 10:10:46 +0800 Subject: [PATCH 1/2] PI: New parameter content --- .../android/finsky/IntegrityExtensions.kt | 131 +++++++++++++++++- .../ExpressIntegrityService.kt | 110 +++++++++------ .../ExpressIntegritySession.kt | 10 +- .../IntermediateIntegrity.kt | 4 +- .../model/StandardIntegrityException.kt | 18 +++ vending-app/src/main/proto/Integrity.proto | 61 +++++++- 6 files changed, 279 insertions(+), 55 deletions(-) create mode 100644 vending-app/src/main/kotlin/com/google/android/finsky/model/StandardIntegrityException.kt 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 df9c796799..89fd251d35 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 @@ -8,9 +8,11 @@ package com.google.android.finsky import android.accounts.AccountManager import android.accounts.AccountManagerFuture import android.content.Context +import android.content.pm.ApplicationInfo import android.content.pm.PackageInfo import android.content.pm.PackageManager import android.content.pm.Signature +import android.net.ConnectivityManager import android.os.Bundle import android.security.keystore.KeyGenParameterSpec import android.security.keystore.KeyProperties @@ -21,6 +23,7 @@ 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.gms.droidguard.DroidGuard import com.google.android.gms.droidguard.internal.DroidGuardResultsRequest import com.google.android.gms.tasks.await @@ -35,7 +38,9 @@ import kotlinx.coroutines.withContext import okio.ByteString 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.vending.billing.DEFAULT_ACCOUNT_TYPE import org.microg.vending.billing.GServices import org.microg.vending.billing.core.HttpClient @@ -49,6 +54,7 @@ import java.security.KeyPair import java.security.KeyPairGenerator import java.security.KeyStore import java.security.MessageDigest +import java.security.NoSuchAlgorithmException import java.security.ProviderException import java.security.spec.ECGenParameterSpec import kotlin.coroutines.resume @@ -103,6 +109,15 @@ private fun Context.getProtoFile(): File { return file } +fun Context.isNetworkConnected(): Boolean { + return try { + val connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + connectivityManager.activeNetworkInfo?.isConnected == true + } catch (_: RuntimeException) { + false + } +} + fun PackageManager.getPackageInfoCompat(packageName: String, flags: Int = 0): PackageInfo { return runCatching { if (Build.VERSION.SDK_INT >= 33) { @@ -141,6 +156,15 @@ fun ByteArray.encodeBase64(noPadding: Boolean, noWrap: Boolean = true, urlSafe: return Base64.encodeToString(this, flags) } +fun ByteArray.md5(): ByteArray? { + return try { + val md5 = MessageDigest.getInstance("MD5") + md5.digest(this) + } catch (e: NoSuchAlgorithmException) { + null + } +} + fun ByteArray.sha256(): ByteArray { return MessageDigest.getInstance("SHA-256").digest(this) } @@ -149,6 +173,11 @@ fun Bundle.getPlayCoreVersion() = PlayCoreVersion( major = getInt(KEY_VERSION_MAJOR, 0), minor = getInt(KEY_VERSION_MINOR, 0), patch = getInt(KEY_VERSION_PATCH, 0) ) +fun List?.ensureContainsLockBootloader(): List { + if (isNullOrEmpty()) return listOf(AdviceType.LOCK_BOOTLOADER) + return if (contains(AdviceType.LOCK_BOOTLOADER)) this else listOf(AdviceType.LOCK_BOOTLOADER) + this +} + fun readAes128GcmBuilderFromClientKey(clientKey: ClientKey?): Aead? { if (clientKey == null) { return null @@ -235,7 +264,6 @@ fun fetchCertificateChain(context: Context, attestationChallenge: ByteArray?): L } suspend fun updateLocalExpressFilePB(context: Context, intermediateIntegrityResponseData: IntermediateIntegrityResponseData) = withContext(Dispatchers.IO) { - Log.d(TAG, "Writing AAR to express cache") val intermediateIntegrity = intermediateIntegrityResponseData.intermediateIntegrity val expressFilePB = FileInputStream(context.getProtoFile()).use { input -> ExpressFilePB.ADAPTER.decode(input) } @@ -244,17 +272,13 @@ suspend fun updateLocalExpressFilePB(context: Context, intermediateIntegrityResp packageName = intermediateIntegrity.packageName cloudProjectNumber = intermediateIntegrity.cloudProjectNumber callerKey = intermediateIntegrity.callerKey - webViewRequestMode = intermediateIntegrity.webViewRequestMode.let { - when (it) { - in 0..2 -> it + 1 - else -> 1 - } - } - 1 + webViewRequestMode = intermediateIntegrity.webViewRequestMode.takeIf { it in 0..2 } ?: 0 deviceIntegrityWrapper = DeviceIntegrityWrapper.Builder().apply { creationTime = intermediateIntegrity.callerKey.generated serverGenerated = intermediateIntegrity.serverGenerated deviceIntegrityToken = intermediateIntegrity.intermediateToken }.build() + intermediateIntegrity.integrityAdvice?.let { advice = it } }.build() val requestList = expressFilePB.integrityRequestWrapper.toMutableList() @@ -484,3 +508,96 @@ suspend fun requestIntermediateIntegrity( adapter = IntermediateIntegrityResponseWrapperExtend.ADAPTER ) } + +fun buildClientKeyExtend( + context: Context, + session: ExpressIntegritySession, + packageInformation: PackageInformation, + clientKey: ClientKey +): ClientKeyExtend { + return ClientKeyExtend.Builder().apply { + cloudProjectNumber = session.cloudProjectNumber + keySetHandle = clientKey.keySetHandle + if (session.webViewRequestMode == 2) { + this.optPackageName = KEY_OPT_PACKAGE + this.versionCode = 0 + } else { + this.optPackageName = session.packageName + this.versionCode = packageInformation.versionCode + this.certificateSha256Hashes = packageInformation.certificateSha256Hashes + } + this.deviceSerialHash = ProfileManager.getSerial(context).toByteArray().sha256().toByteString() + }.build() +} + +fun buildInstallSourceMetaData( + context: Context, + packageName: String +): InstallSourceMetaData { + fun resolveInstallerType(name: String?): InstallerType = when { + name.isNullOrEmpty() -> InstallerType.UNSPECIFIED_INSTALLER + name == Constants.VENDING_PACKAGE_NAME -> InstallerType.PHONESKY_INSTALLER + else -> InstallerType.OTHER_INSTALLER + } + + fun resolvePackageSourceType(type: Int): PackageSourceType = when (type) { + 1 -> PackageSourceType.PACKAGE_SOURCE_OTHER + 2 -> PackageSourceType.PACKAGE_SOURCE_STORE + 3 -> PackageSourceType.PACKAGE_SOURCE_LOCAL_FILE + 4 -> PackageSourceType.PACKAGE_SOURCE_DOWNLOADED_FILE + else -> PackageSourceType.PACKAGE_SOURCE_UNSPECIFIED + } + + val builder = InstallSourceMetaData.Builder().apply { + installingPackageName = InstallerType.UNSPECIFIED_INSTALLER + initiatingPackageName = InstallerType.UNSPECIFIED_INSTALLER + originatingPackageName = InstallerType.UNSPECIFIED_INSTALLER + updateOwnerPackageName = InstallerType.UNSPECIFIED_INSTALLER + packageSourceType = PackageSourceType.PACKAGE_SOURCE_UNSPECIFIED + } + + val applicationInfo = runCatching { + context.packageManager.getApplicationInfo(packageName, 0) + }.getOrNull() + + if (Build.VERSION.SDK_INT >= 30) { + runCatching { + val info = context.packageManager.getInstallSourceInfo(packageName) + builder.apply { + initiatingPackageName = resolveInstallerType(info.initiatingPackageName) + installingPackageName = resolveInstallerType(info.installingPackageName) + originatingPackageName = resolveInstallerType(info.originatingPackageName) + + if (Build.VERSION.SDK_INT >= 34) { + updateOwnerPackageName = resolveInstallerType(info.updateOwnerPackageName) + } + if (Build.VERSION.SDK_INT >= 33) { + packageSourceType = resolvePackageSourceType(info.packageSource) + } + } + } + } else { + builder.installingPackageName = runCatching { + resolveInstallerType(context.packageManager.getInstallerPackageName(packageName)) + }.getOrElse { InstallerType.UNSPECIFIED_INSTALLER } + } + + builder.appFlags = applicationInfo?.let { info -> + buildList { + if (info.flags and ApplicationInfo.FLAG_SYSTEM != 0) add(SystemAppFlag.FLAG_SYSTEM) + if (info.flags and ApplicationInfo.FLAG_UPDATED_SYSTEM_APP != 0) { + add(SystemAppFlag.FLAG_UPDATED_SYSTEM_APP) + } + }.ifEmpty { listOf(SystemAppFlag.SYSTEM_APP_INFO_UNSPECIFIED) } + } ?: listOf(SystemAppFlag.SYSTEM_APP_INFO_UNSPECIFIED) + + return builder.build() +} + +fun validateIntermediateIntegrityResponse(intermediateIntegrityResponse: IntermediateIntegrityResponseData) { + val intermediateIntegrity = intermediateIntegrityResponse.intermediateIntegrity + + requireNotNull(intermediateIntegrity.intermediateToken) { "Null intermediateToken" } + requireNotNull(intermediateIntegrity.serverGenerated) { "Null serverGenerated" } +} + 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 386dfe5976..6d6ee3207d 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 @@ -26,7 +26,9 @@ 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.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_NONCE @@ -42,12 +44,19 @@ import com.google.android.finsky.PlayProtectDetails import com.google.android.finsky.PlayProtectState import com.google.android.finsky.RESULT_UN_AUTH 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.encodeBase64 +import com.google.android.finsky.ensureContainsLockBootloader import com.google.android.finsky.getAuthToken import com.google.android.finsky.getIntegrityRequestWrapper import com.google.android.finsky.getPackageInfoCompat +import com.google.android.finsky.isNetworkConnected +import com.google.android.finsky.md5 import com.google.android.finsky.model.IntegrityErrorCode +import com.google.android.finsky.model.StandardIntegrityException import com.google.android.finsky.readAes128GcmBuilderFromClientKey import com.google.android.finsky.requestIntermediateIntegrity import com.google.android.finsky.sha256 @@ -56,6 +65,7 @@ import com.google.android.finsky.updateExpressAuthTokenWrapper import com.google.android.finsky.updateExpressClientKey import com.google.android.finsky.updateExpressSessionTime import com.google.android.finsky.updateLocalExpressFilePB +import com.google.android.finsky.validateIntermediateIntegrityResponse import com.google.android.play.core.integrity.protocol.IExpressIntegrityService import com.google.android.play.core.integrity.protocol.IExpressIntegrityServiceCallback import com.google.android.play.core.integrity.protocol.IRequestDialogCallback @@ -89,11 +99,9 @@ private class ExpressIntegrityServiceImpl(private val context: Context, override override fun warmUpIntegrityToken(bundle: Bundle, callback: IExpressIntegrityServiceCallback?) { lifecycleScope.launchWhenCreated { runCatching { - val authToken = getAuthToken(context, AUTH_TOKEN_SCOPE) - if (TextUtils.isEmpty(authToken)) { - Log.w(TAG, "warmUpIntegrityToken: Got null auth token for type: $AUTH_TOKEN_SCOPE") + if (!context.isNetworkConnected()) { + throw StandardIntegrityException(IntegrityErrorCode.NETWORK_ERROR, "No network is available") } - Log.d(TAG, "warmUpIntegrityToken authToken: $authToken") val expressIntegritySession = ExpressIntegritySession( packageName = bundle.getString(KEY_PACKAGE_NAME) ?: "", @@ -104,9 +112,18 @@ private class ExpressIntegrityServiceImpl(private val context: Context, override null, webViewRequestMode = bundle.getInt(KEY_REQUEST_MODE, 0) ) + Log.d(TAG, "warmUpIntegrityToken session:$expressIntegritySession}") + updateExpressSessionTime(context, expressIntegritySession, refreshWarmUpMethodTime = true, refreshRequestMethodTime = false) val clientKey = updateExpressClientKey(context) + + val authToken = getAuthToken(context, AUTH_TOKEN_SCOPE) + if (TextUtils.isEmpty(authToken)) { + Log.w(TAG, "warmUpIntegrityToken: Got null auth token for type: $AUTH_TOKEN_SCOPE") + } + Log.d(TAG, "warmUpIntegrityToken authToken: $authToken") + val expressFilePB = updateExpressAuthTokenWrapper(context, expressIntegritySession, authToken, clientKey) val tokenWrapper = expressFilePB.tokenWrapper ?: AuthTokenWrapper() @@ -123,14 +140,14 @@ private class ExpressIntegrityServiceImpl(private val context: Context, override val deviceIntegrity = deviceIntegrityAndExpiredKey.deviceIntegrity if (deviceIntegrity.deviceIntegrityToken?.size == 0 || deviceIntegrity.clientKey?.keySetHandle?.size == 0) { - throw RuntimeException("DroidGuard token is empty.") + throw StandardIntegrityException("DroidGuard token is empty.") } val deviceKeyMd5 = Base64.encodeToString( deviceIntegrity.clientKey?.keySetHandle?.md5()?.toByteArray(), Base64.NO_PADDING or Base64.NO_WRAP or Base64.URL_SAFE ) if (deviceKeyMd5.isNullOrEmpty()) { - throw RuntimeException("Null deviceKeyMd5.") + throw StandardIntegrityException("Null deviceKeyMd5.") } val deviceIntegrityResponse = DeviceIntegrityResponse( @@ -145,35 +162,16 @@ private class ExpressIntegrityServiceImpl(private val context: Context, override } val packageInformation = PackageInformation(certificateSha256Hashes, packageInfo.versionCode) - - val clientKeyExtend = ClientKeyExtend.Builder().apply { - cloudProjectNumber = expressIntegritySession.cloudProjectNumber - keySetHandle = clientKey.keySetHandle - if (expressIntegritySession.webViewRequestMode == 2) { - this.optPackageName = KEY_OPT_PACKAGE - this.versionCode = 0 - } else { - this.optPackageName = expressIntegritySession.packageName - this.versionCode = packageInformation.versionCode - this.certificateSha256Hashes = packageInformation.certificateSha256Hashes - } - }.build() - -// val certificateChainList = fetchCertificateChain(context, clientKeyExtend.keySetHandle?.sha256()?.toByteArray()) - - val sessionId = expressIntegritySession.sessionId - val playCoreVersion = bundle.getPlayCoreVersion() - - Log.d(TAG, "warmUpIntegrityToken sessionId:$sessionId") - + val clientKeyExtend = buildClientKeyExtend(context, expressIntegritySession, packageInformation, clientKey) val intermediateIntegrityRequest = IntermediateIntegrityRequest.Builder().apply { deviceIntegrityToken(deviceIntegrityResponse.deviceIntegrity.deviceIntegrityToken) readAes128GcmBuilderFromClientKey(deviceIntegrityResponse.deviceIntegrity.clientKey)?.let { clientKeyExtendBytes(it.encrypt(clientKeyExtend.encode(), null).toByteString()) } - playCoreVersion(playCoreVersion) - sessionId(sessionId) -// certificateChainWrapper(IntermediateIntegrityRequest.CertificateChainWrapper(certificateChainList)) + playCoreVersion(bundle.getPlayCoreVersion()) + sessionId(expressIntegritySession.sessionId) + installSourceMetaData(buildInstallSourceMetaData(context, expressIntegritySession.packageName)) + cloudProjectNumber(expressIntegritySession.cloudProjectNumber) playProtectDetails(PlayProtectDetails(PlayProtectState.PLAY_PROTECT_STATE_NONE)) if (expressIntegritySession.webViewRequestMode != 0) { requestMode(RequestMode.Builder().mode(expressIntegritySession.webViewRequestMode.takeIf { it in 0..2 } ?: 0).build()) @@ -183,9 +181,19 @@ private class ExpressIntegrityServiceImpl(private val context: Context, override Log.d(TAG, "intermediateIntegrityRequest: $intermediateIntegrityRequest") val intermediateIntegrityResponse = requestIntermediateIntegrity(context, authToken, intermediateIntegrityRequest).intermediateIntegrityResponseWrapper?.intermediateIntegrityResponse - ?: throw RuntimeException("intermediateIntegrityResponse is null.") - - Log.d(TAG, "requestIntermediateIntegrity: $intermediateIntegrityResponse") + ?: IntermediateIntegrityResponse() + + Log.d(TAG, "requestIntermediateIntegrity response: ${intermediateIntegrityResponse.encode().encodeBase64(true)}") + + val errorCode = intermediateIntegrityResponse.errorInfo?.let { error -> + if (error.errorCode == null) { + null + } else if (error.testErrorType == TestErrorType.REQUEST_EXPRESS) { + error.errorCode + } else if (error.testErrorType == TestErrorType.WARMUP) { + throw StandardIntegrityException(error.errorCode, "Server-specified exception") + } else null + } val defaultAccountName: String = runCatching { if (expressIntegritySession.webViewRequestMode != 0) { @@ -195,30 +203,41 @@ private class ExpressIntegrityServiceImpl(private val context: Context, override } }.getOrDefault(RESULT_UN_AUTH) + val callerKeyMd5 = clientKey.encode().md5() ?: throw StandardIntegrityException("Null callerKeyMd5") + val refreshClientKey = clientKey.newBuilder() + .generated(makeTimestamp(System.currentTimeMillis())) + .build() + val fixedAdvice = IntegrityAdvice.Builder() + .advices(intermediateIntegrityResponse.integrityAdvice?.advices.ensureContainsLockBootloader()) + .build() + val intermediateIntegrityResponseData = IntermediateIntegrityResponseData( intermediateIntegrity = IntermediateIntegrity( expressIntegritySession.packageName, expressIntegritySession.cloudProjectNumber, defaultAccountName, - clientKey, + refreshClientKey, intermediateIntegrityResponse.intermediateToken, intermediateIntegrityResponse.serverGenerated, expressIntegritySession.webViewRequestMode, - 0 - ), - callerKeyMd5 = Base64.encodeToString( - clientKey.encode(), Base64.URL_SAFE or Base64.NO_WRAP or Base64.NO_PADDING + errorCode, + fixedAdvice ), + callerKeyMd5 = callerKeyMd5.encodeBase64(noPadding = true), appVersionCode = packageInformation.versionCode, deviceIntegrityResponse = deviceIntegrityResponse, appAccessRiskVerdictEnabled = intermediateIntegrityResponse.appAccessRiskVerdictEnabled ) + validateIntermediateIntegrityResponse(intermediateIntegrityResponseData) + updateLocalExpressFilePB(context, intermediateIntegrityResponseData) - callback?.onWarmResult(bundleOf(KEY_WARM_UP_SID to sessionId)) + callback?.onWarmResult(bundleOf(KEY_WARM_UP_SID to expressIntegritySession.sessionId)) }.onFailure { - callback?.onWarmResult(bundleOf(KEY_ERROR to IntegrityErrorCode.INTEGRITY_TOKEN_PROVIDER_INVALID)) + val exception = it as? StandardIntegrityException ?: StandardIntegrityException(it.message) + Log.w(TAG, "warm up has failed: code=${exception.code}, message=${exception.message}", exception) + callback?.onWarmResult(bundleOf(KEY_ERROR to exception.code)) } } } @@ -237,6 +256,8 @@ private class ExpressIntegrityServiceImpl(private val context: Context, override webViewRequestMode = bundle.getInt(KEY_REQUEST_MODE, 0) ) + Log.d(TAG, "requestExpressIntegrityToken session:$expressIntegritySession}") + if (TextUtils.isEmpty(expressIntegritySession.packageName)) { Log.w(TAG, "packageName is empty.") callback?.onRequestResult(bundleOf(KEY_ERROR to IntegrityErrorCode.INTERNAL_ERROR)) @@ -272,6 +293,10 @@ private class ExpressIntegrityServiceImpl(private val context: Context, override return@launchWhenCreated } + integrityRequestWrapper.deviceIntegrityWrapper?.errorCode?.let { + throw StandardIntegrityException(it, "Server-specified exception") + } + val integritySession = IntermediateIntegritySession.Builder().creationTime(makeTimestamp(System.currentTimeMillis())).requestHash(expressIntegritySession.requestHash) .sessionId(Random.nextBytes(8).toByteString()).timestampMillis(0).build() @@ -295,8 +320,9 @@ private class ExpressIntegrityServiceImpl(private val context: Context, override ) ) }.onFailure { - Log.e(TAG, "requesting token has failed for ${bundle.getString(KEY_PACKAGE_NAME)}.") - callback?.onRequestResult(bundleOf(KEY_ERROR to IntegrityErrorCode.INTEGRITY_TOKEN_PROVIDER_INVALID)) + val exception = it as? StandardIntegrityException ?: StandardIntegrityException(it.message) + Log.w(TAG, "requesting token has failed: code=${exception.code}, message=${exception.message}", exception) + callback?.onRequestResult(bundleOf(KEY_ERROR to exception.code)) } } } diff --git a/vending-app/src/main/kotlin/com/google/android/finsky/expressintegrityservice/ExpressIntegritySession.kt b/vending-app/src/main/kotlin/com/google/android/finsky/expressintegrityservice/ExpressIntegritySession.kt index 1add64bb15..3c973a4aad 100644 --- a/vending-app/src/main/kotlin/com/google/android/finsky/expressintegrityservice/ExpressIntegritySession.kt +++ b/vending-app/src/main/kotlin/com/google/android/finsky/expressintegrityservice/ExpressIntegritySession.kt @@ -12,4 +12,12 @@ data class ExpressIntegritySession( var originatingWarmUpSessionId: Long, var verdictOptOut: List?, var webViewRequestMode: Int -) \ No newline at end of file +) { + override fun toString(): String { + return "ExpressIntegritySession(packageName='$packageName', cloudProjectNumber=$cloudProjectNumber, sessionId=$sessionId, requestHash=$requestHash, originatingWarmUpSessionId=$originatingWarmUpSessionId, verdictOptOut=${ + verdictOptOut?.joinToString( + prefix = "[", postfix = "]" + ) + }, webViewRequestMode=$webViewRequestMode)" + } +} \ No newline at end of file diff --git a/vending-app/src/main/kotlin/com/google/android/finsky/expressintegrityservice/IntermediateIntegrity.kt b/vending-app/src/main/kotlin/com/google/android/finsky/expressintegrityservice/IntermediateIntegrity.kt index 21e807fee7..8606762aa9 100644 --- a/vending-app/src/main/kotlin/com/google/android/finsky/expressintegrityservice/IntermediateIntegrity.kt +++ b/vending-app/src/main/kotlin/com/google/android/finsky/expressintegrityservice/IntermediateIntegrity.kt @@ -6,6 +6,7 @@ package com.google.android.finsky.expressintegrityservice import com.google.android.finsky.ClientKey +import com.google.android.finsky.IntegrityAdvice import okio.ByteString import org.microg.vending.proto.Timestamp @@ -17,5 +18,6 @@ data class IntermediateIntegrity( var intermediateToken: ByteString?, var serverGenerated: Timestamp?, var webViewRequestMode: Int, - var testErrorCode: Int + var testErrorCode: Int?, + var integrityAdvice: IntegrityAdvice? ) \ No newline at end of file diff --git a/vending-app/src/main/kotlin/com/google/android/finsky/model/StandardIntegrityException.kt b/vending-app/src/main/kotlin/com/google/android/finsky/model/StandardIntegrityException.kt new file mode 100644 index 0000000000..35d1ba6d6a --- /dev/null +++ b/vending-app/src/main/kotlin/com/google/android/finsky/model/StandardIntegrityException.kt @@ -0,0 +1,18 @@ +/** + * SPDX-FileCopyrightText: 2025 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.finsky.model + +class StandardIntegrityException : Exception { + val code: Int + + constructor(code: Int, message: String) : super(message) { + this.code = code + } + + constructor(cause: String?) : super(cause) { + this.code = IntegrityErrorCode.INTERNAL_ERROR + } +} \ No newline at end of file diff --git a/vending-app/src/main/proto/Integrity.proto b/vending-app/src/main/proto/Integrity.proto index 5f38797872..4e2e5fcaba 100644 --- a/vending-app/src/main/proto/Integrity.proto +++ b/vending-app/src/main/proto/Integrity.proto @@ -116,18 +116,49 @@ message MetricsUpdate { optional int32 bytesStored = 4; } +enum InstallerType { + UNSPECIFIED_INSTALLER = 0; + PHONESKY_INSTALLER = 1; + OTHER_INSTALLER = 2; +} + +enum PackageSourceType { + PACKAGE_SOURCE_UNSPECIFIED = 0; + PACKAGE_SOURCE_STORE = 1; + PACKAGE_SOURCE_LOCAL_FILE = 2; + PACKAGE_SOURCE_DOWNLOADED_FILE = 3; + PACKAGE_SOURCE_OTHER = 4; +} + +enum SystemAppFlag { + SYSTEM_APP_INFO_UNSPECIFIED = 0; + FLAG_SYSTEM = 1; + FLAG_UPDATED_SYSTEM_APP = 2; +} + +message InstallSourceMetaData { + optional InstallerType initiatingPackageName = 1; + optional InstallerType installingPackageName = 2; + optional InstallerType originatingPackageName = 3; + optional InstallerType updateOwnerPackageName = 4; + repeated SystemAppFlag appFlags = 5; + optional PackageSourceType packageSourceType = 6; +} + message IntermediateIntegrityRequest { optional bytes clientKeyExtendBytes = 1; optional bytes deviceIntegrityToken = 2; optional PlayCoreVersion playCoreVersion = 3; optional PlayProtectDetails playProtectDetails = 4; - // optional bytes c = 5; + optional bytes expiredDeviceKey = 5; optional RequestMode requestMode = 6; optional int64 sessionId = 7; message CertificateChainWrapper { repeated bytes certificateChains = 1; } optional CertificateChainWrapper certificateChainWrapper = 8; + optional InstallSourceMetaData installSourceMetaData = 9; + optional int64 cloudProjectNumber = 10; } message IntermediateIntegrityResponseWrapperExtend { @@ -138,11 +169,25 @@ message IntermediateIntegrityResponseWrapper { optional IntermediateIntegrityResponse intermediateIntegrityResponse = 221; } +enum AdviceType { + DEVICE_INTEGRITY_ADVICE_UNSPECIFIED = 0; + RESTORE_TO_FACTORY_ROM = 1; + LOCK_BOOTLOADER = 2; + REBOOT_DEVICE = 3; + GENERIC_NON_ACTIONABLE_ADVICE = 4; + INSTALL_SYSTEM_UPDATE = 5; +} + +message IntegrityAdvice { + repeated AdviceType advices = 1 [packed=true]; +} + message IntermediateIntegrityResponse { optional bytes intermediateToken = 1; optional Timestamp serverGenerated = 2; optional bool appAccessRiskVerdictEnabled = 4; - optional ErrorResponse errorInfo = 5; + optional TestErrorResponse errorInfo = 5; + optional IntegrityAdvice integrityAdvice = 6; } message ExpressIntegrityResponse { @@ -151,9 +196,14 @@ message ExpressIntegrityResponse { optional bytes appAccessRiskDetailsResponse = 4; } -message ErrorResponse { +enum TestErrorType { + WARMUP = 1; + REQUEST_EXPRESS = 2; +} + +message TestErrorResponse { optional int32 errorCode = 1; - optional int32 status = 2; + optional TestErrorType testErrorType = 2; } message IntermediateIntegritySession { @@ -202,6 +252,7 @@ message IntegrityRequestWrapper { optional string accountName = 4; optional uint64 cloudProjectNumber = 5; optional int32 webViewRequestMode = 7; + optional IntegrityAdvice advice = 8; } message ExpressFilePB { @@ -232,6 +283,8 @@ message ClientKeyExtend { repeated string certificateSha256Hashes = 3; optional int64 cloudProjectNumber = 4; optional bytes keySetHandle = 5; + optional bytes deviceSerialHash = 6; + optional InstallSourceMetaData installSourceMetaData = 7; } message TokenRequestWrapper { From 1c912e1abfa87ef81109c719ea8e5480a9d4db6f Mon Sep 17 00:00:00 2001 From: Marvin W Date: Wed, 24 Sep 2025 15:47:00 +0200 Subject: [PATCH 2/2] Fix post merge --- .../finsky/expressintegrityservice/ExpressIntegrityService.kt | 4 ---- 1 file changed, 4 deletions(-) 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 59934ac5dc..524e389f5e 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 @@ -212,9 +212,6 @@ private class ExpressIntegrityServiceImpl(private val context: Context, override val fixedAdvice = IntegrityAdvice.Builder() .advices(intermediateIntegrityResponse.integrityAdvice?.advices.ensureContainsLockBootloader()) .build() - val refreshClientKey = clientKey.newBuilder() - .generated(makeTimestamp(System.currentTimeMillis())) - .build() val intermediateIntegrityResponseData = IntermediateIntegrityResponseData( intermediateIntegrity = IntermediateIntegrity( expressIntegritySession.packageName, @@ -226,7 +223,6 @@ private class ExpressIntegrityServiceImpl(private val context: Context, override expressIntegritySession.webViewRequestMode, errorCode, fixedAdvice - 0 ), callerKeyMd5 = callerKeyMd5.encodeBase64(noPadding = true), appVersionCode = packageInformation.versionCode,