From 6d45bfb7ed1a80db539ecd9bde492eeebd891b08 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Thu, 7 Oct 2021 10:20:42 +0200 Subject: [PATCH] Add DroidGuard support --- build.gradle | 2 +- .../java/org/microg/gms/common/Build.java | 44 +- .../org/microg/gms/common/PackageUtils.java | 12 + .../microg/gms/settings/SettingsContract.kt | 24 + .../microg/gms/settings/SettingsProvider.kt | 63 ++- .../microg/gms/utils/PackageManagerWrapper.kt | 529 ++++++++++++++++++ play-services-core/build.gradle | 2 + .../src/main/AndroidManifest.xml | 19 +- .../java/org/microg/gms/auth/AuthRequest.java | 2 +- .../org/microg/gms/checkin/CheckinClient.java | 2 +- .../gms/droidguard/DroidGuardService.java | 38 -- .../gms/droidguard/DroidGuardPreferences.kt | 70 --- .../gms/droidguard/DroidGuardResultCreator.kt | 70 --- .../org/microg/gms/droidguard/ServiceInfo.kt | 93 --- .../gms/safetynet/SafetyNetClientService.kt | 31 +- .../internal/DroidGuardInitReply.java | 10 +- .../build.gradle | 33 ++ .../src/main/proto/droidguard.proto | 73 +++ play-services-droidguard-core/build.gradle | 59 ++ .../src/main/AndroidManifest.xml | 28 + .../droidguard/DroidGuardChimeraService.java | 127 +++++ .../tracing/wrapper/TracingIntentService.java | 57 ++ .../microg/gms/droidguard/GuardCallback.java | 78 +++ .../microg/gms/droidguard/MediaDrmLock.java | 10 + .../microg/gms/droidguard/BytesException.kt | 22 + .../microg/gms/droidguard/DgDatabaseHelper.kt | 68 +++ .../gms/droidguard/DgpDatabaseHelper.kt | 19 + .../gms/droidguard/DroidGuardHandleImpl.kt | 112 ++++ .../gms/droidguard/DroidGuardPreferences.kt | 39 ++ .../gms/droidguard/DroidGuardResultCreator.kt | 72 +++ .../gms/droidguard/DroidGuardService.kt | 11 + .../gms/droidguard/DroidGuardServiceBroker.kt | 26 + .../gms/droidguard/DroidGuardServiceImpl.kt | 39 ++ .../microg/gms/droidguard/FallbackCreator.kt | 33 ++ .../org/microg/gms/droidguard/HandleProxy.kt | 53 ++ .../gms/droidguard/HandleProxyFactory.kt | 215 +++++++ .../gms/droidguard/SignatureVerifier.kt | 31 + .../org/microg/gms/droidguard/VersionUtil.kt | 75 +++ .../microg/gms/droidguard/DroidGuardClient.kt | 1 - .../gms/droidguard/DroidGuardClientImpl.kt | 3 +- settings.gradle | 2 + 41 files changed, 1970 insertions(+), 327 deletions(-) create mode 100644 play-services-base-core/src/main/kotlin/org/microg/gms/utils/PackageManagerWrapper.kt delete mode 100644 play-services-core/src/main/java/org/microg/gms/droidguard/DroidGuardService.java delete mode 100644 play-services-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardPreferences.kt delete mode 100644 play-services-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardResultCreator.kt delete mode 100644 play-services-core/src/main/kotlin/org/microg/gms/droidguard/ServiceInfo.kt create mode 100644 play-services-droidguard-core-proto/build.gradle create mode 100644 play-services-droidguard-core-proto/src/main/proto/droidguard.proto create mode 100644 play-services-droidguard-core/build.gradle create mode 100644 play-services-droidguard-core/src/main/AndroidManifest.xml create mode 100644 play-services-droidguard-core/src/main/java/com/google/android/gms/droidguard/DroidGuardChimeraService.java create mode 100644 play-services-droidguard-core/src/main/java/com/google/android/gms/framework/tracing/wrapper/TracingIntentService.java create mode 100644 play-services-droidguard-core/src/main/java/org/microg/gms/droidguard/GuardCallback.java create mode 100644 play-services-droidguard-core/src/main/java/org/microg/gms/droidguard/MediaDrmLock.java create mode 100644 play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/BytesException.kt create mode 100644 play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DgDatabaseHelper.kt create mode 100644 play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DgpDatabaseHelper.kt create mode 100644 play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardHandleImpl.kt create mode 100644 play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardPreferences.kt create mode 100644 play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardResultCreator.kt create mode 100644 play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardService.kt create mode 100644 play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardServiceBroker.kt create mode 100644 play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardServiceImpl.kt create mode 100644 play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/FallbackCreator.kt create mode 100644 play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/HandleProxy.kt create mode 100644 play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/HandleProxyFactory.kt create mode 100644 play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/SignatureVerifier.kt create mode 100644 play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/VersionUtil.kt diff --git a/build.gradle b/build.gradle index f6fabfa8..92d7db8c 100644 --- a/build.gradle +++ b/build.gradle @@ -27,7 +27,7 @@ buildscript { ext.supportLibraryVersion = '28.0.0' ext.slf4jVersion = '1.7.25' - ext.volleyVersion = '1.2.0' + ext.volleyVersion = '1.2.1' ext.wireVersion = '3.2.2' ext.androidBuildGradleVersion = '4.1.0' diff --git a/play-services-base-core/src/main/java/org/microg/gms/common/Build.java b/play-services-base-core/src/main/java/org/microg/gms/common/Build.java index 532a6845..c297c295 100644 --- a/play-services-base-core/src/main/java/org/microg/gms/common/Build.java +++ b/play-services-base-core/src/main/java/org/microg/gms/common/Build.java @@ -16,32 +16,40 @@ package org.microg.gms.common; +import android.annotation.TargetApi; + import java.util.Locale; import java.util.Random; +// TODO: Make flexible public class Build { + public String board = android.os.Build.BOARD; + public String bootloader = android.os.Build.BOOTLOADER; + public String brand = android.os.Build.BRAND; + public String cpu_abi = android.os.Build.CPU_ABI; + public String cpu_abi2 = android.os.Build.CPU_ABI2; + @TargetApi(21) + public String[] supported_abis = android.os.Build.VERSION.SDK_INT >= 21 ? android.os.Build.SUPPORTED_ABIS : new String[0]; + public String device = android.os.Build.DEVICE; + public String display = android.os.Build.DISPLAY; public String fingerprint = android.os.Build.FINGERPRINT; public String hardware = android.os.Build.HARDWARE; - public String brand = android.os.Build.BRAND; - public String radio = getRadio(); - public String bootloader = android.os.Build.BOOTLOADER; - public long time = android.os.Build.TIME; - public String device = android.os.Build.DEVICE; - public int sdk = android.os.Build.VERSION.SDK_INT; - public String model = android.os.Build.MODEL; - public String manufacturer = android.os.Build.MANUFACTURER; - public String product = android.os.Build.PRODUCT; + public String host = android.os.Build.HOST; public String id = android.os.Build.ID; + public String manufacturer = android.os.Build.MANUFACTURER; + public String model = android.os.Build.MODEL; + public String product = android.os.Build.PRODUCT; + public String radio = android.os.Build.RADIO; public String serial = generateSerialNumber(); // TODO: static - - @SuppressWarnings("deprecation") - private static String getRadio() { - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH) { - return android.os.Build.getRadioVersion(); - } else { - return android.os.Build.RADIO; - } - } + public String tags = android.os.Build.TAGS; + public long time = android.os.Build.TIME; + public String type = android.os.Build.TYPE; + public String user = android.os.Build.USER; + public String version_codename = android.os.Build.VERSION.CODENAME; + public String version_incremental = android.os.Build.VERSION.INCREMENTAL; + public String version_release = android.os.Build.VERSION.RELEASE; + public String version_sdk = android.os.Build.VERSION.SDK; + public int version_sdk_int = android.os.Build.VERSION.SDK_INT; private String generateSerialNumber() { String serial = "008741"; diff --git a/play-services-base-core/src/main/java/org/microg/gms/common/PackageUtils.java b/play-services-base-core/src/main/java/org/microg/gms/common/PackageUtils.java index 4abbcd2a..08a0db17 100644 --- a/play-services-base-core/src/main/java/org/microg/gms/common/PackageUtils.java +++ b/play-services-base-core/src/main/java/org/microg/gms/common/PackageUtils.java @@ -152,6 +152,18 @@ public class PackageUtils { return getAndCheckCallingPackage(context, suggestedPackageName, 0); } + @Nullable + public static String getAndCheckCallingPackageOrExtendedAccess(Context context, String suggestedPackageName) { + try { + return getAndCheckCallingPackage(context, suggestedPackageName, 0); + } catch (Exception e) { + if (callerHasExtendedAccess(context)) { + return suggestedPackageName; + } + throw e; + } + } + @Nullable public static String getAndCheckCallingPackage(Context context, int suggestedCallerUid) { return getAndCheckCallingPackage(context, null, suggestedCallerUid); 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 1287c3ea..7132c7cf 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 @@ -107,6 +107,28 @@ object SettingsContract { private const val id = "safety-net" fun getContentUri(context: Context) = Uri.withAppendedPath(getAuthorityUri(context), id) fun getContentType(context: Context) = "vnd.android.cursor.item/vnd.${getAuthority(context)}.$id" + + const val ENABLED = "safetynet_enabled" + + val PROJECTION = arrayOf( + ENABLED + ) + } + + object DroidGuard { + private const val id = "droidguard" + fun getContentUri(context: Context) = Uri.withAppendedPath(getAuthorityUri(context), id) + fun getContentType(context: Context) = "vnd.android.cursor.item/vnd.${getAuthority(context)}.$id" + + const val ENABLED = "droidguard_enabled" + const val MODE = "droidguard_mode" + const val NETWORK_SERVER_URL = "droidguard_network_server_url" + + val PROJECTION = arrayOf( + ENABLED, + MODE, + NETWORK_SERVER_URL + ) } private fun withoutCallingIdentity(f: () -> T): T { @@ -118,6 +140,7 @@ object SettingsContract { } } + @JvmStatic fun getSettings(context: Context, uri: Uri, projection: Array?, f: (Cursor) -> T): T = withoutCallingIdentity { context.contentResolver.query(uri, projection, null, null, null).use { c -> require(c != null) { "Cursor for query $uri ${projection?.toList()} was null" } @@ -126,6 +149,7 @@ object SettingsContract { } } + @JvmStatic fun setSettings(context: Context, uri: Uri, v: ContentValues.() -> Unit) = withoutCallingIdentity { val values = ContentValues().apply { v.invoke(this) } val affected = context.contentResolver.update(uri, values, null, null) 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 b59da582..3d1f712f 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 @@ -17,8 +17,10 @@ import android.preference.PreferenceManager import org.microg.gms.common.PackageUtils.warnIfNotMainProcess import org.microg.gms.settings.SettingsContract.Auth import org.microg.gms.settings.SettingsContract.CheckIn +import org.microg.gms.settings.SettingsContract.DroidGuard import org.microg.gms.settings.SettingsContract.Exposure import org.microg.gms.settings.SettingsContract.Gcm +import org.microg.gms.settings.SettingsContract.SafetyNet import org.microg.gms.settings.SettingsContract.getAuthority import java.io.File @@ -61,6 +63,8 @@ class SettingsProvider : ContentProvider() { Gcm.getContentUri(context!!) -> queryGcm(projection ?: Gcm.PROJECTION) Auth.getContentUri(context!!) -> queryAuth(projection ?: Auth.PROJECTION) Exposure.getContentUri(context!!) -> queryExposure(projection ?: Exposure.PROJECTION) + SafetyNet.getContentUri(context!!) -> querySafetyNet(projection ?: SafetyNet.PROJECTION) + DroidGuard.getContentUri(context!!) -> queryDroidGuard(projection ?: DroidGuard.PROJECTION) else -> null } @@ -77,6 +81,8 @@ class SettingsProvider : ContentProvider() { Gcm.getContentUri(context!!) -> updateGcm(values) Auth.getContentUri(context!!) -> updateAuth(values) Exposure.getContentUri(context!!) -> updateExposure(values) + SafetyNet.getContentUri(context!!) -> updateSafetyNet(values) + DroidGuard.getContentUri(context!!) -> updateDroidGuard(values) else -> return 0 } return 1 @@ -216,9 +222,51 @@ class SettingsProvider : ContentProvider() { editor.apply() } + private fun querySafetyNet(p: Array): Cursor = MatrixCursor(p).addRow(p) { key -> + when (key) { + SafetyNet.ENABLED -> getSettingsBoolean(key, false) + else -> throw IllegalArgumentException("Unknown key: $key") + } + } + + private fun updateSafetyNet(values: ContentValues) { + if (values.size() == 0) return + val editor = preferences.edit() + values.valueSet().forEach { (key, value) -> + when (key) { + SafetyNet.ENABLED -> editor.putBoolean(key, value as Boolean) + else -> throw IllegalArgumentException("Unknown key: $key") + } + } + editor.apply() + } + + private fun queryDroidGuard(p: Array): Cursor = MatrixCursor(p).addRow(p) { key -> + when (key) { + DroidGuard.ENABLED -> getSettingsBoolean(key, false) + DroidGuard.MODE -> getSettingsString(key) + DroidGuard.NETWORK_SERVER_URL -> getSettingsString(key) + else -> throw IllegalArgumentException("Unknown key: $key") + } + } + + private fun updateDroidGuard(values: ContentValues) { + if (values.size() == 0) return + val editor = preferences.edit() + values.valueSet().forEach { (key, value) -> + when (key) { + DroidGuard.ENABLED -> editor.putBoolean(key, value as Boolean) + DroidGuard.MODE -> editor.putString(key, value as String) + DroidGuard.NETWORK_SERVER_URL -> editor.putString(key, value as String) + else -> throw IllegalArgumentException("Unknown key: $key") + } + } + editor.apply() + } + private fun MatrixCursor.addRow( p: Array, - valueGetter: (String) -> Any + valueGetter: (String) -> Any? ): MatrixCursor { val row = newRow() for (key in p) row.add(valueGetter.invoke(key)) @@ -243,7 +291,16 @@ class SettingsProvider : ContentProvider() { * @return the current setting as [Int], because [ContentProvider] does not support [Boolean]. */ private fun getSettingsBoolean(key: String, def: Boolean): Int { - val default = systemDefaultPreferences?.getBoolean(key, def) ?: def - return if (preferences.getBoolean(key, default)) 1 else 0 + return listOf(preferences, systemDefaultPreferences).getBooleanAsInt(key, def) } + + private fun getSettingsString(key: String, def: String? = null): String? = listOf(preferences, systemDefaultPreferences).getString(key, def) + private fun getSettingsInt(key: String, def: Int): Int = listOf(preferences, systemDefaultPreferences).getInt(key, def) + private fun getSettingsLong(key: String, def: Long): Long = listOf(preferences, systemDefaultPreferences).getLong(key, def) + + private fun List.getString(key: String, def: String?): String? = foldRight(def) { preferences, defValue -> preferences?.getString(key, defValue) ?: defValue } + private fun List.getInt(key: String, def: Int): Int = foldRight(def) { preferences, defValue -> preferences?.getInt(key, defValue) ?: defValue } + private fun List.getLong(key: String, def: Long): Long = foldRight(def) { preferences, defValue -> preferences?.getLong(key, defValue) ?: defValue } + private fun List.getBoolean(key: String, def: Boolean): Boolean = foldRight(def) { preferences, defValue -> preferences?.getBoolean(key, defValue) ?: defValue } + private fun List.getBooleanAsInt(key: String, def: Boolean): Int = if (getBoolean(key, def)) 1 else 0 } diff --git a/play-services-base-core/src/main/kotlin/org/microg/gms/utils/PackageManagerWrapper.kt b/play-services-base-core/src/main/kotlin/org/microg/gms/utils/PackageManagerWrapper.kt new file mode 100644 index 00000000..4a958a57 --- /dev/null +++ b/play-services-base-core/src/main/kotlin/org/microg/gms/utils/PackageManagerWrapper.kt @@ -0,0 +1,529 @@ +/* + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ +package org.microg.gms.utils + +import android.annotation.TargetApi +import android.content.ComponentName +import android.content.Intent +import android.content.IntentFilter +import android.content.pm.* +import android.content.res.Resources +import android.content.res.XmlResourceParser +import android.graphics.Rect +import android.graphics.drawable.Drawable +import android.os.Bundle +import android.os.UserHandle +import androidx.annotation.RequiresApi + +open class PackageManagerWrapper(private val wrapped: PackageManager) : PackageManager() { + override fun getPackageInfo(packageName: String, flags: Int): PackageInfo { + return wrapped.getPackageInfo(packageName, flags) + } + + @TargetApi(26) + override fun getPackageInfo(versionedPackage: VersionedPackage, flags: Int): PackageInfo { + return wrapped.getPackageInfo(versionedPackage, flags) + } + + override fun currentToCanonicalPackageNames(packageNames: Array): Array { + return wrapped.currentToCanonicalPackageNames(packageNames) + } + + override fun canonicalToCurrentPackageNames(packageNames: Array): Array { + return wrapped.canonicalToCurrentPackageNames(packageNames) + } + + override fun getLaunchIntentForPackage(packageName: String): Intent? { + return wrapped.getLaunchIntentForPackage(packageName) + } + + @TargetApi(21) + override fun getLeanbackLaunchIntentForPackage(packageName: String): Intent? { + return wrapped.getLeanbackLaunchIntentForPackage(packageName) + } + + override fun getPackageGids(packageName: String): IntArray { + return wrapped.getPackageGids(packageName) + } + + @TargetApi(24) + override fun getPackageGids(packageName: String, flags: Int): IntArray { + return wrapped.getPackageGids(packageName, flags) + } + + @TargetApi(24) + override fun getPackageUid(packageName: String, flags: Int): Int { + return wrapped.getPackageUid(packageName, flags) + } + + override fun getPermissionInfo(permName: String, flags: Int): PermissionInfo { + return wrapped.getPermissionInfo(permName, flags) + } + + override fun queryPermissionsByGroup(permissionGroup: String, flags: Int): MutableList { + return wrapped.queryPermissionsByGroup(permissionGroup, flags) + } + + override fun getPermissionGroupInfo(permName: String, flags: Int): PermissionGroupInfo { + return wrapped.getPermissionGroupInfo(permName, flags) + } + + override fun getAllPermissionGroups(flags: Int): MutableList { + return wrapped.getAllPermissionGroups(flags) + } + + override fun getApplicationInfo(packageName: String, flags: Int): ApplicationInfo { + return wrapped.getApplicationInfo(packageName, flags) + } + + override fun getActivityInfo(component: ComponentName, flags: Int): ActivityInfo { + return wrapped.getActivityInfo(component, flags) + } + + override fun getReceiverInfo(component: ComponentName, flags: Int): ActivityInfo { + return wrapped.getReceiverInfo(component, flags) + } + + override fun getServiceInfo(component: ComponentName, flags: Int): ServiceInfo { + return wrapped.getServiceInfo(component, flags) + } + + override fun getProviderInfo(component: ComponentName, flags: Int): ProviderInfo { + return wrapped.getProviderInfo(component, flags) + } + + @RequiresApi(29) + override fun getInstalledModules(flags: Int): MutableList { + return wrapped.getInstalledModules(flags) + } + + override fun getInstalledPackages(flags: Int): MutableList { + return wrapped.getInstalledPackages(flags) + } + + @TargetApi(18) + override fun getPackagesHoldingPermissions(permissions: Array, flags: Int): MutableList { + return wrapped.getPackagesHoldingPermissions(permissions, flags) + } + + override fun checkPermission(permName: String, packageName: String): Int { + return wrapped.checkPermission(permName, packageName) + } + + @TargetApi(23) + override fun isPermissionRevokedByPolicy(permName: String, packageName: String): Boolean { + return wrapped.isPermissionRevokedByPolicy(permName, packageName) + } + + override fun addPermission(info: PermissionInfo): Boolean { + return wrapped.addPermission(info) + } + + override fun addPermissionAsync(info: PermissionInfo): Boolean { + return wrapped.addPermissionAsync(info) + } + + override fun removePermission(permName: String) { + return wrapped.removePermission(permName) + } + + override fun checkSignatures(packageName1: String, packageName2: String): Int { + return wrapped.checkSignatures(packageName1, packageName2) + } + + override fun checkSignatures(uid1: Int, uid2: Int): Int { + return wrapped.checkSignatures(uid1, uid2) + } + + override fun getPackagesForUid(uid: Int): Array? { + return wrapped.getPackagesForUid(uid) + } + + override fun getNameForUid(uid: Int): String? { + return wrapped.getNameForUid(uid) + } + + override fun getInstalledApplications(flags: Int): MutableList { + return wrapped.getInstalledApplications(flags) + } + + @TargetApi(26) + override fun isInstantApp(): Boolean { + return wrapped.isInstantApp + } + + @TargetApi(26) + override fun isInstantApp(packageName: String): Boolean { + return wrapped.isInstantApp(packageName) + } + + @TargetApi(26) + override fun getInstantAppCookieMaxBytes(): Int { + return wrapped.instantAppCookieMaxBytes + } + + @TargetApi(26) + override fun getInstantAppCookie(): ByteArray { + return wrapped.instantAppCookie + } + + @TargetApi(26) + override fun clearInstantAppCookie() { + return wrapped.clearInstantAppCookie() + } + + @TargetApi(26) + override fun updateInstantAppCookie(cookie: ByteArray?) { + return wrapped.updateInstantAppCookie(cookie) + } + + @TargetApi(26) + override fun getSystemSharedLibraryNames(): Array? { + return wrapped.systemSharedLibraryNames + } + + @TargetApi(26) + override fun getSharedLibraries(flags: Int): MutableList { + return wrapped.getSharedLibraries(flags) + } + + @TargetApi(26) + override fun getChangedPackages(sequenceNumber: Int): ChangedPackages? { + return wrapped.getChangedPackages(sequenceNumber) + } + + override fun getSystemAvailableFeatures(): Array { + return wrapped.systemAvailableFeatures + } + + override fun hasSystemFeature(featureName: String): Boolean { + return wrapped.hasSystemFeature(featureName) + } + + @TargetApi(24) + override fun hasSystemFeature(featureName: String, version: Int): Boolean { + return wrapped.hasSystemFeature(featureName, version) + } + + override fun resolveActivity(intent: Intent, flags: Int): ResolveInfo? { + return wrapped.resolveActivity(intent, flags) + } + + override fun queryIntentActivities(intent: Intent, flags: Int): MutableList { + return wrapped.queryIntentActivities(intent, flags) + } + + override fun queryIntentActivityOptions(caller: ComponentName?, specifics: Array?, intent: Intent, flags: Int): MutableList { + return wrapped.queryIntentActivityOptions(caller, specifics, intent, flags) + } + + override fun queryBroadcastReceivers(intent: Intent, flags: Int): MutableList { + return wrapped.queryBroadcastReceivers(intent, flags) + } + + override fun resolveService(intent: Intent, flags: Int): ResolveInfo? { + return wrapped.resolveService(intent, flags) + } + + override fun queryIntentServices(intent: Intent, flags: Int): MutableList { + return wrapped.queryIntentServices(intent, flags) + } + + @TargetApi(19) + override fun queryIntentContentProviders(intent: Intent, flags: Int): MutableList { + return wrapped.queryIntentContentProviders(intent, flags) + } + + override fun resolveContentProvider(authority: String, flags: Int): ProviderInfo? { + return wrapped.resolveContentProvider(authority, flags) + } + + override fun queryContentProviders(processName: String?, uid: Int, flags: Int): MutableList { + return wrapped.queryContentProviders(processName, uid, flags) + } + + override fun getInstrumentationInfo(className: ComponentName, flags: Int): InstrumentationInfo { + return wrapped.getInstrumentationInfo(className, flags) + } + + override fun queryInstrumentation(targetPackage: String, flags: Int): MutableList { + return wrapped.queryInstrumentation(targetPackage, flags) + } + + override fun getDrawable(packageName: String, resid: Int, appInfo: ApplicationInfo?): Drawable? { + return wrapped.getDrawable(packageName, resid, appInfo) + } + + override fun getActivityIcon(activityName: ComponentName): Drawable { + return wrapped.getActivityIcon(activityName) + } + + override fun getActivityIcon(intent: Intent): Drawable { + return wrapped.getActivityIcon(intent) + } + + @TargetApi(20) + override fun getActivityBanner(activityName: ComponentName): Drawable? { + return wrapped.getActivityBanner(activityName) + } + + @TargetApi(20) + override fun getActivityBanner(intent: Intent): Drawable? { + return wrapped.getActivityBanner(intent) + } + + override fun getDefaultActivityIcon(): Drawable { + return wrapped.defaultActivityIcon + } + + override fun getApplicationIcon(info: ApplicationInfo): Drawable { + return wrapped.getApplicationIcon(info) + } + + override fun getApplicationIcon(packageName: String): Drawable { + return wrapped.getApplicationIcon(packageName) + } + + @TargetApi(20) + override fun getApplicationBanner(info: ApplicationInfo): Drawable? { + return wrapped.getApplicationBanner(info) + } + + @TargetApi(20) + override fun getApplicationBanner(packageName: String): Drawable? { + return wrapped.getApplicationBanner(packageName) + } + + override fun getActivityLogo(activityName: ComponentName): Drawable? { + return wrapped.getActivityLogo(activityName) + } + + override fun getActivityLogo(intent: Intent): Drawable? { + return wrapped.getActivityLogo(intent) + } + + override fun getApplicationLogo(info: ApplicationInfo): Drawable? { + return wrapped.getApplicationLogo(info) + } + + override fun getApplicationLogo(packageName: String): Drawable? { + return wrapped.getApplicationLogo(packageName) + } + + @TargetApi(21) + override fun getUserBadgedIcon(drawable: Drawable, user: UserHandle): Drawable { + return wrapped.getUserBadgedIcon(drawable, user) + } + + @TargetApi(21) + override fun getUserBadgedDrawableForDensity(drawable: Drawable, user: UserHandle, badgeLocation: Rect?, badgeDensity: Int): Drawable { + return wrapped.getUserBadgedDrawableForDensity(drawable, user, badgeLocation, badgeDensity) + } + + @TargetApi(21) + override fun getUserBadgedLabel(label: CharSequence, user: UserHandle): CharSequence { + return wrapped.getUserBadgedLabel(label, user) + } + + override fun getText(packageName: String, resid: Int, appInfo: ApplicationInfo?): CharSequence? { + return wrapped.getText(packageName, resid, appInfo) + } + + override fun getXml(packageName: String, resid: Int, appInfo: ApplicationInfo?): XmlResourceParser? { + return wrapped.getXml(packageName, resid, appInfo) + } + + override fun getApplicationLabel(info: ApplicationInfo): CharSequence { + return wrapped.getApplicationLabel(info) + } + + override fun getResourcesForActivity(activityName: ComponentName): Resources { + return wrapped.getResourcesForActivity(activityName) + } + + override fun getResourcesForApplication(app: ApplicationInfo): Resources { + return wrapped.getResourcesForApplication(app) + } + + override fun getResourcesForApplication(packageName: String): Resources { + return wrapped.getResourcesForApplication(packageName) + } + + override fun verifyPendingInstall(id: Int, verificationCode: Int) { + return wrapped.verifyPendingInstall(id, verificationCode) + } + + @TargetApi(17) + override fun extendVerificationTimeout(id: Int, verificationCodeAtTimeout: Int, millisecondsToDelay: Long) { + return wrapped.extendVerificationTimeout(id, verificationCodeAtTimeout, millisecondsToDelay) + } + + override fun setInstallerPackageName(targetPackage: String, installerPackageName: String?) { + return wrapped.setInstallerPackageName(targetPackage, installerPackageName) + } + + override fun getInstallerPackageName(packageName: String): String? { + return wrapped.getInstallerPackageName(packageName) + } + + override fun addPackageToPreferred(packageName: String) { + return wrapped.addPackageToPreferred(packageName) + } + + override fun removePackageFromPreferred(packageName: String) { + return wrapped.removePackageFromPreferred(packageName) + } + + override fun getPreferredPackages(flags: Int): MutableList { + return wrapped.getPreferredPackages(flags) + } + + override fun addPreferredActivity(filter: IntentFilter, match: Int, set: Array?, activity: ComponentName) { + return wrapped.addPreferredActivity(filter, match, set, activity) + } + + override fun clearPackagePreferredActivities(packageName: String) { + return wrapped.clearPackagePreferredActivities(packageName) + } + + override fun getPreferredActivities(outFilters: MutableList, outActivities: MutableList, packageName: String?): Int { + return wrapped.getPreferredActivities(outFilters, outActivities, packageName) + } + + override fun setComponentEnabledSetting(componentName: ComponentName, newState: Int, flags: Int) { + return wrapped.setComponentEnabledSetting(componentName, newState, flags) + } + + override fun getComponentEnabledSetting(componentName: ComponentName): Int { + return wrapped.getComponentEnabledSetting(componentName) + } + + override fun setApplicationEnabledSetting(packageName: String, newState: Int, flags: Int) { + return wrapped.setApplicationEnabledSetting(packageName, newState, flags) + } + + override fun getApplicationEnabledSetting(packageName: String): Int { + return wrapped.getApplicationEnabledSetting(packageName) + } + + override fun isSafeMode(): Boolean { + return wrapped.isSafeMode + } + + @TargetApi(26) + override fun setApplicationCategoryHint(packageName: String, categoryHint: Int) { + return wrapped.setApplicationCategoryHint(packageName, categoryHint) + } + + @TargetApi(21) + override fun getPackageInstaller(): PackageInstaller { + return wrapped.packageInstaller + } + + @TargetApi(26) + override fun canRequestPackageInstalls(): Boolean { + return wrapped.canRequestPackageInstalls() + } + + + @TargetApi(29) + override fun addWhitelistedRestrictedPermission(packageName: String, permName: String, whitelistFlags: Int): Boolean { + return wrapped.addWhitelistedRestrictedPermission(packageName, permName, whitelistFlags) + } + + @TargetApi(30) + override fun getBackgroundPermissionOptionLabel(): CharSequence { + return wrapped.getBackgroundPermissionOptionLabel() + } + + @TargetApi(30) + override fun getInstallSourceInfo(packageName: String): InstallSourceInfo { + return wrapped.getInstallSourceInfo(packageName) + } + + @TargetApi(30) + override fun getMimeGroup(mimeGroup: String): MutableSet { + return wrapped.getMimeGroup(mimeGroup) + } + + @TargetApi(29) + override fun getModuleInfo(packageName: String, flags: Int): ModuleInfo { + return wrapped.getModuleInfo(packageName, flags) + } + + override fun getPackageArchiveInfo(archiveFilePath: String, flags: Int): PackageInfo? { + return wrapped.getPackageArchiveInfo(archiveFilePath, flags) + } + + @TargetApi(28) + override fun getSuspendedPackageAppExtras(): Bundle? { + return wrapped.suspendedPackageAppExtras + } + + @TargetApi(29) + override fun getSyntheticAppDetailsActivityEnabled(packageName: String): Boolean { + return wrapped.getSyntheticAppDetailsActivityEnabled(packageName) + } + + @TargetApi(29) + override fun getWhitelistedRestrictedPermissions(packageName: String, whitelistFlag: Int): MutableSet { + return wrapped.getWhitelistedRestrictedPermissions(packageName, whitelistFlag) + } + + @TargetApi(28) + override fun hasSigningCertificate(packageName: String, certificate: ByteArray, type: Int): Boolean { + return wrapped.hasSigningCertificate(packageName, certificate, type) + } + + @TargetApi(28) + override fun hasSigningCertificate(uid: Int, certificate: ByteArray, type: Int): Boolean { + return wrapped.hasSigningCertificate(uid, certificate, type) + } + + @TargetApi(30) + override fun isAutoRevokeWhitelisted(): Boolean { + return wrapped.isAutoRevokeWhitelisted + } + + @TargetApi(30) + override fun isAutoRevokeWhitelisted(packageName: String): Boolean { + return wrapped.isAutoRevokeWhitelisted(packageName) + } + + @TargetApi(30) + override fun isDefaultApplicationIcon(drawable: Drawable): Boolean { + return wrapped.isDefaultApplicationIcon(drawable) + } + + @TargetApi(29) + override fun isDeviceUpgrading(): Boolean { + return wrapped.isDeviceUpgrading + } + + @TargetApi(28) + override fun isPackageSuspended(): Boolean { + return wrapped.isPackageSuspended + } + + @TargetApi(29) + override fun isPackageSuspended(packageName: String): Boolean { + return wrapped.isPackageSuspended(packageName) + } + + @TargetApi(29) + override fun removeWhitelistedRestrictedPermission(packageName: String, permName: String, whitelistFlags: Int): Boolean { + return wrapped.removeWhitelistedRestrictedPermission(packageName, permName, whitelistFlags) + } + + @TargetApi(30) + override fun setAutoRevokeWhitelisted(packageName: String, whitelisted: Boolean): Boolean { + return wrapped.setAutoRevokeWhitelisted(packageName, whitelisted) + } + + @TargetApi(30) + override fun setMimeGroup(mimeGroup: String, mimeTypes: MutableSet) { + return wrapped.setMimeGroup(mimeGroup, mimeTypes) + } +} diff --git a/play-services-core/build.gradle b/play-services-core/build.gradle index 9f87ea76..1209c603 100644 --- a/play-services-core/build.gradle +++ b/play-services-core/build.gradle @@ -44,6 +44,8 @@ dependencies { implementation project(':play-services-base-core-ui') implementation project(':play-services-conscrypt-provider-core') implementation project(':play-services-cronet-core') + implementation project(':play-services-droidguard') // TODO: Move to play-services-safetynet-core once we have it + implementation project(':play-services-droidguard-core') implementation project(':play-services-location-core') withNearbyImplementation project(':play-services-nearby-core') withNearbyImplementation project(':play-services-nearby-core-ui') diff --git a/play-services-core/src/main/AndroidManifest.xml b/play-services-core/src/main/AndroidManifest.xml index 693ec889..e4fa697d 100644 --- a/play-services-core/src/main/AndroidManifest.xml +++ b/play-services-core/src/main/AndroidManifest.xml @@ -26,10 +26,12 @@ + android:permissionGroup="android.permission-group.NETWORK" + android:protectionLevel="privileged|signature" /> + + + @@ -294,16 +299,6 @@ - - - - - - - - - - diff --git a/play-services-core/src/main/java/org/microg/gms/auth/AuthRequest.java b/play-services-core/src/main/java/org/microg/gms/auth/AuthRequest.java index 8dda8e4c..b65648cb 100644 --- a/play-services-core/src/main/java/org/microg/gms/auth/AuthRequest.java +++ b/play-services-core/src/main/java/org/microg/gms/auth/AuthRequest.java @@ -92,7 +92,7 @@ public class AuthRequest extends HttpFormClient.Request { } public AuthRequest build(Build build) { - sdkVersion = build.sdk; + sdkVersion = build.version_sdk_int; deviceName = build.device; buildVersion = build.id; return this; diff --git a/play-services-core/src/main/java/org/microg/gms/checkin/CheckinClient.java b/play-services-core/src/main/java/org/microg/gms/checkin/CheckinClient.java index 0b56919f..9186d1a3 100644 --- a/play-services-core/src/main/java/org/microg/gms/checkin/CheckinClient.java +++ b/play-services-core/src/main/java/org/microg/gms/checkin/CheckinClient.java @@ -97,7 +97,7 @@ public class CheckinClient { //.packageVersionCode(Constants.MAX_REFERENCE_VERSION) .product(build.product) .radio(build.radio) - .sdkVersion(build.sdk) + .sdkVersion(build.version_sdk_int) .time(build.time / 1000) .build()) .cellOperator(phoneInfo.cellOperator) diff --git a/play-services-core/src/main/java/org/microg/gms/droidguard/DroidGuardService.java b/play-services-core/src/main/java/org/microg/gms/droidguard/DroidGuardService.java deleted file mode 100644 index 7760862d..00000000 --- a/play-services-core/src/main/java/org/microg/gms/droidguard/DroidGuardService.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2013-2017 microG Project Team - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.microg.gms.droidguard; - -import android.util.Log; - -import com.google.android.gms.common.internal.GetServiceRequest; -import com.google.android.gms.common.internal.IGmsCallbacks; - -import org.microg.gms.BaseService; -import org.microg.gms.common.GmsService; - -public class DroidGuardService extends BaseService { - - public DroidGuardService() { - super("GmsDroidGuardSvc", GmsService.DROIDGUARD); - } - - @Override - public void handleServiceRequest(IGmsCallbacks callback, GetServiceRequest request, GmsService service) { - // TODO - Log.d(TAG, "handleServiceRequest"); - } -} diff --git a/play-services-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardPreferences.kt b/play-services-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardPreferences.kt deleted file mode 100644 index 1d9fd028..00000000 --- a/play-services-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardPreferences.kt +++ /dev/null @@ -1,70 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021, microG Project Team - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.microg.gms.droidguard - -import android.content.Context -import android.content.SharedPreferences -import java.io.File - -class DroidGuardPreferences(private val context: Context) { - @Suppress("DEPRECATION") - private val preferences by lazy { context.getSharedPreferences("droidguard", Context.MODE_PRIVATE) } - private val systemDefaultPreferences by lazy { - try { - Context::class.java.getDeclaredMethod("getSharedPreferences", File::class.java, Int::class.javaPrimitiveType).invoke(context, File("/system/etc/microg.xml"), Context.MODE_PRIVATE) as SharedPreferences - } catch (ignored: Exception) { - null - } - } - private var editing: Boolean = false - private val updates: MutableMap = hashMapOf() - - var mode: Mode - get() = try { - getSettingsString(PREF_DROIDGUARD_MODE)?.let { Mode.valueOf(it) } ?: Mode.Connector - } catch (e: Exception) { - Mode.Connector - } - set(value) { - if (editing) updates[PREF_DROIDGUARD_MODE] = value.name - } - - var networkServerUrl: String? - get() = getSettingsString(PREF_DROIDGUARD_NETWORK_SERVER_URL) - set(value) { - if (editing) updates[PREF_DROIDGUARD_NETWORK_SERVER_URL] = value - } - - private fun getSettingsString(key: String): String? { - return systemDefaultPreferences?.getString(key, null) ?: preferences.getString(key, null) - } - - fun edit(commands: DroidGuardPreferences.() -> Unit) { - editing = true - commands(this) - preferences.edit().also { - for ((k, v) in updates) { - when (v) { - is String -> it.putString(k, v) - null -> it.remove(k) - } - } - }.apply() - editing = false - } - - enum class Mode { - Disabled, - Connector, - Network - } - - companion object { - const val PREF_DROIDGUARD_MODE = "droidguard_mode" - const val PREF_DROIDGUARD_NETWORK_SERVER_URL = "droidguard_network_server_url" - } -} - diff --git a/play-services-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardResultCreator.kt b/play-services-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardResultCreator.kt deleted file mode 100644 index db3f80cd..00000000 --- a/play-services-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardResultCreator.kt +++ /dev/null @@ -1,70 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021, microG Project Team - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.microg.gms.droidguard - -import android.content.Context -import android.os.Bundle -import android.util.Base64 -import com.android.volley.VolleyError -import com.android.volley.toolbox.StringRequest -import com.android.volley.toolbox.Volley -import org.microg.gms.checkin.LastCheckinInfo -import kotlin.coroutines.resume -import kotlin.coroutines.resumeWithException -import kotlin.coroutines.suspendCoroutine - -interface DroidGuardResultCreator { - suspend fun getResult(flow: String, data: Map): ByteArray - - companion object { - fun getInstance(context: Context): DroidGuardResultCreator = when (DroidGuardPreferences(context).mode) { - DroidGuardPreferences.Mode.Disabled -> throw RuntimeException("DroidGuard disabled") - DroidGuardPreferences.Mode.Connector -> ConnectorDroidGuardResultCreator(context) - DroidGuardPreferences.Mode.Network -> NetworkDroidGuardResultCreator(context) - } - - suspend fun getResult(context: Context, flow: String, data: Map): ByteArray = - getInstance(context).getResult(flow, data) - } -} - -private class ConnectorDroidGuardResultCreator(private val context: Context) : DroidGuardResultCreator { - override suspend fun getResult(flow: String, data: Map): ByteArray = suspendCoroutine { continuation -> - Thread { - val bundle = Bundle() - for (entry in data) { - bundle.putString(entry.key, entry.value) - } - val conn = RemoteDroidGuardConnector(context) - val dg = conn.guard(flow, LastCheckinInfo.read(context).androidId.toString(), bundle) - if (dg == null) { - continuation.resumeWithException(RuntimeException("No DroidGuard result")) - } else if (dg.statusCode == 0 && dg.result != null) { - continuation.resume(dg.result) - } else { - continuation.resumeWithException(RuntimeException("Status: " + dg.statusCode + ", error:" + dg.errorMsg)) - } - }.start() - } -} - -private class NetworkDroidGuardResultCreator(private val context: Context) : DroidGuardResultCreator { - private val queue = Volley.newRequestQueue(context) - private val url: String - get() = DroidGuardPreferences(context).networkServerUrl ?: throw RuntimeException("Network URL required") - - override suspend fun getResult(flow: String, data: Map): ByteArray = suspendCoroutine { continuation -> - queue.add(PostParamsStringRequest("$url?flow=$flow", data, { - continuation.resume(Base64.decode(it, Base64.NO_WRAP + Base64.NO_PADDING + Base64.URL_SAFE)) - }, { - continuation.resumeWithException(RuntimeException(it)) - })) - } -} - -class PostParamsStringRequest(url: String, private val data: Map, listener: (String) -> Unit, errorListener: (VolleyError) -> Unit) : StringRequest(Method.POST, url, listener, errorListener) { - override fun getParams(): Map = data -} diff --git a/play-services-core/src/main/kotlin/org/microg/gms/droidguard/ServiceInfo.kt b/play-services-core/src/main/kotlin/org/microg/gms/droidguard/ServiceInfo.kt deleted file mode 100644 index c73dde72..00000000 --- a/play-services-core/src/main/kotlin/org/microg/gms/droidguard/ServiceInfo.kt +++ /dev/null @@ -1,93 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2020, microG Project Team - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.microg.gms.droidguard - -import android.content.BroadcastReceiver -import android.content.Context -import android.content.Intent -import android.content.IntentFilter -import android.util.Log -import java.io.Serializable -import kotlin.coroutines.resume -import kotlin.coroutines.resumeWithException -import kotlin.coroutines.suspendCoroutine - -private const val ACTION_SERVICE_INFO_REQUEST = "org.microg.gms.droidguard.SERVICE_INFO_REQUEST" -private const val ACTION_UPDATE_CONFIGURATION = "org.microg.gms.droidguard.UPDATE_CONFIGURATION" -private const val ACTION_SERVICE_INFO_RESPONSE = "org.microg.gms.droidguard.SERVICE_INFO_RESPONSE" -private const val EXTRA_SERVICE_INFO = "org.microg.gms.droidguard.SERVICE_INFO" -private const val EXTRA_CONFIGURATION = "org.microg.gms.droidguard.CONFIGURATION" -private const val TAG = "GmsGcmStatusInfo" - -data class ServiceInfo(val configuration: ServiceConfiguration) : Serializable - -data class ServiceConfiguration(val mode: DroidGuardPreferences.Mode, val networkServerUrl: String?) : Serializable { - fun saveToPrefs(context: Context) { - DroidGuardPreferences(context).edit { - mode = this@ServiceConfiguration.mode - networkServerUrl = this@ServiceConfiguration.networkServerUrl - } - } -} - -private fun DroidGuardPreferences.toConfiguration(): ServiceConfiguration = ServiceConfiguration(mode, networkServerUrl) - -class ServiceInfoReceiver : BroadcastReceiver() { - private fun sendInfoResponse(context: Context) { - context.sendOrderedBroadcast(Intent(ACTION_SERVICE_INFO_RESPONSE).apply { - setPackage(context.packageName) - putExtra(EXTRA_SERVICE_INFO, ServiceInfo(DroidGuardPreferences(context).toConfiguration())) - }, null) - } - - override fun onReceive(context: Context, intent: Intent) { - try { - when (intent.action) { - ACTION_UPDATE_CONFIGURATION -> { - (intent.getSerializableExtra(EXTRA_CONFIGURATION) as? ServiceConfiguration)?.saveToPrefs(context) - } - } - sendInfoResponse(context) - } catch (e: Exception) { - Log.w(TAG, e) - } - } -} - -private suspend fun sendToServiceInfoReceiver(intent: Intent, context: Context): ServiceInfo = suspendCoroutine { - context.registerReceiver(object : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - context.unregisterReceiver(this) - val serviceInfo = try { - intent.getSerializableExtra(EXTRA_SERVICE_INFO) as ServiceInfo - } catch (e: Exception) { - it.resumeWithException(e) - return - } - try { - it.resume(serviceInfo) - } catch (e: Exception) { - Log.w(TAG, e) - } - } - }, IntentFilter(ACTION_SERVICE_INFO_RESPONSE)) - try { - context.sendOrderedBroadcast(intent, null) - } catch (e: Exception) { - it.resumeWithException(e) - } -} - -suspend fun getDroidGuardServiceInfo(context: Context): ServiceInfo = sendToServiceInfoReceiver( - Intent(context, ServiceInfoReceiver::class.java).apply { - action = ACTION_SERVICE_INFO_REQUEST - }, context) - -suspend fun setDroidGuardServiceConfiguration(context: Context, configuration: ServiceConfiguration): ServiceInfo = sendToServiceInfoReceiver( - Intent(context, ServiceInfoReceiver::class.java).apply { - action = ACTION_UPDATE_CONFIGURATION - putExtra(EXTRA_CONFIGURATION, configuration) - }, context) diff --git a/play-services-core/src/main/kotlin/org/microg/gms/safetynet/SafetyNetClientService.kt b/play-services-core/src/main/kotlin/org/microg/gms/safetynet/SafetyNetClientService.kt index 3426fbb2..69e745f6 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/safetynet/SafetyNetClientService.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/safetynet/SafetyNetClientService.kt @@ -29,6 +29,7 @@ import org.microg.gms.BaseService import org.microg.gms.checkin.LastCheckinInfo import org.microg.gms.common.GmsService import org.microg.gms.common.PackageUtils +import org.microg.gms.droidguard.DroidGuardPreferences import org.microg.gms.droidguard.DroidGuardResultCreator import org.microg.gms.recaptcha.ReCaptchaActivity import org.microg.gms.recaptcha.appendUrlEncodedParam @@ -57,8 +58,15 @@ class SafetyNetClientServiceImpl(private val context: Context, private val packa callbacks.onAttestationData(Status(CommonStatusCodes.DEVELOPER_ERROR), null) return } + if (!SafetyNetPrefs.get(context).isEnabled) { - Log.d(TAG, "ignoring SafetyNet request, it's disabled") + Log.d(TAG, "ignoring SafetyNet request, SafetyNet is disabled") + callbacks.onAttestationData(Status.CANCELED, null) + return + } + + if (!DroidGuardPreferences.isEnabled(context)) { + Log.d(TAG, "ignoring SafetyNet request, DroidGuard is disabled") callbacks.onAttestationData(Status.CANCELED, null) return } @@ -67,17 +75,12 @@ class SafetyNetClientServiceImpl(private val context: Context, private val packa try { val attestation = Attestation(context, packageName) attestation.buildPayload(nonce) - try { - val dg = DroidGuardResultCreator.getResult(context, "attest", mapOf("contentBinding" to attestation.payloadHashBase64)) - attestation.setDroidGaurdResult(Base64.encodeToString(dg, Base64.NO_WRAP + Base64.NO_PADDING + Base64.URL_SAFE)) - } catch (e: Exception) { - if (SafetyNetPrefs.get(context).isOfficial) throw e - Log.w(TAG, e) - null - } - val data = withContext(Dispatchers.IO) { AttestationData(attestation.attest(apiKey)) } - callbacks.onAttestationData(Status.SUCCESS, data) - } catch (e: IOException) { + val data = mapOf("contentBinding" to attestation.payloadHashBase64) + val dg = withContext(Dispatchers.IO) { DroidGuardResultCreator.getResult(context, "attest", data) } + attestation.setDroidGaurdResult(Base64.encodeToString(dg, Base64.NO_WRAP + Base64.NO_PADDING + Base64.URL_SAFE)) + val resultData = withContext(Dispatchers.IO) { AttestationData(attestation.attest(apiKey)) } + callbacks.onAttestationData(Status.SUCCESS, resultData) + } catch (e: Exception) { Log.w(TAG, e) callbacks.onAttestationData(Status.INTERNAL_ERROR, null) } @@ -112,11 +115,13 @@ class SafetyNetClientServiceImpl(private val context: Context, private val packa callbacks.onAttestationData(Status(CommonStatusCodes.DEVELOPER_ERROR), null) return } + if (!SafetyNetPrefs.get(context).isEnabled) { - Log.d(TAG, "ignoring SafetyNet request, it's disabled") + Log.d(TAG, "ignoring SafetyNet request, SafetyNet is disabled") callbacks.onAttestationData(Status.CANCELED, null) return } + val intent = Intent(context, ReCaptchaActivity::class.java) intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY) intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) diff --git a/play-services-droidguard-api/src/main/java/com/google/android/gms/droidguard/internal/DroidGuardInitReply.java b/play-services-droidguard-api/src/main/java/com/google/android/gms/droidguard/internal/DroidGuardInitReply.java index 02b93b0d..0294a5c0 100644 --- a/play-services-droidguard-api/src/main/java/com/google/android/gms/droidguard/internal/DroidGuardInitReply.java +++ b/play-services-droidguard-api/src/main/java/com/google/android/gms/droidguard/internal/DroidGuardInitReply.java @@ -9,11 +9,13 @@ import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.Parcelable; -public class DroidGuardInitReply implements Parcelable { - public ParcelFileDescriptor pfd; - public Parcelable object; +import androidx.annotation.Nullable; - public DroidGuardInitReply(ParcelFileDescriptor pfd, Parcelable object) { +public class DroidGuardInitReply implements Parcelable { + public @Nullable ParcelFileDescriptor pfd; + public @Nullable Parcelable object; + + public DroidGuardInitReply(@Nullable ParcelFileDescriptor pfd, @Nullable Parcelable object) { this.pfd = pfd; this.object = object; } diff --git a/play-services-droidguard-core-proto/build.gradle b/play-services-droidguard-core-proto/build.gradle new file mode 100644 index 00000000..234ab40a --- /dev/null +++ b/play-services-droidguard-core-proto/build.gradle @@ -0,0 +1,33 @@ +/* + * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +apply plugin: 'com.squareup.wire' +apply plugin: 'kotlin' +apply plugin: 'maven-publish' +apply plugin: 'signing' + +dependencies { + implementation "com.squareup.wire:wire-runtime:$wireVersion" +} + +wire { + kotlin {} +} + +sourceSets { + main.java.srcDirs += "$buildDir/generated/source/wire" +} + +compileKotlin { + kotlinOptions.jvmTarget = 1.8 +} + +compileTestKotlin { + kotlinOptions.jvmTarget = 1.8 +} + +apply from: '../gradle/publish-java.gradle' + +description = 'Protocol buffers for microG implementation of play-services-droidguard' diff --git a/play-services-droidguard-core-proto/src/main/proto/droidguard.proto b/play-services-droidguard-core-proto/src/main/proto/droidguard.proto new file mode 100644 index 00000000..bd2223ad --- /dev/null +++ b/play-services-droidguard-core-proto/src/main/proto/droidguard.proto @@ -0,0 +1,73 @@ +/* + * SPDX-FileCopyrightText: 2016, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +option java_package = "org.microg.gms.droidguard"; + +message Usage { + optional string flow = 1; + optional string packageName = 2; +} + +message KeyValuePair { + optional string key = 1; + optional string val = 2; +} + +message StackTraceElement { + optional string className = 1; + optional string methodName = 2; + optional string fileName = 3; + optional int32 lineNumber = 4; + optional bool isNativeMethod = 5; +} + +message ExceptionInfo { + optional string name = 1; + optional string message = 2; + repeated StackTraceElement stackTrace = 3; +} + +message ExceptionList { + repeated ExceptionInfo exceptions = 1; +} + +message PingData { + optional string field1 = 1; + optional int64 field2 = 2; +} + +message Request { + optional Usage usage = 1; + repeated KeyValuePair info = 2; + optional string versionName = 3; + optional bool hasAccount = 6; + optional bool isGoogleCn = 7; + optional bool enableInlineVm = 8; + repeated bytes cached = 9; + optional bytes field10 = 10; + optional int32 field11 = 11; + optional ExceptionList exceptions = 12; + optional int32 versionCode = 13; + optional string arch = 14; + optional int32 field15 = 15; + optional PingData ping = 16; +} + +message SignedResponse { + optional bytes data = 1; + optional bytes signature = 2; +} + +message Response { + optional bytes byteCode = 1; + optional string vmUrl = 2; + optional bytes vmChecksum = 3; + optional int32 expiryTimeSecs = 4; + optional bytes content = 5; + optional bool save = 6; + optional int32 minWait = 7; + optional int32 maxWait = 8; + optional bytes extra = 9; +} diff --git a/play-services-droidguard-core/build.gradle b/play-services-droidguard-core/build.gradle new file mode 100644 index 00000000..e96945d2 --- /dev/null +++ b/play-services-droidguard-core/build.gradle @@ -0,0 +1,59 @@ +/* + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' +apply plugin: 'maven-publish' +apply plugin: 'signing' + +dependencies { + api project(':play-services-droidguard-api') + + implementation project(':play-services-base-core') + implementation project(':play-services-chimera-core') + implementation project(':play-services-droidguard') + implementation project(':play-services-droidguard-core-proto') + implementation project(':play-services-tasks-ktx') + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion" + + implementation "androidx.core:core-ktx:$coreVersion" + implementation "com.android.volley:volley:$volleyVersion" + implementation "com.squareup.wire:wire-runtime:$wireVersion" +} + +android { + compileSdkVersion androidCompileSdk + buildToolsVersion "$androidBuildVersionTools" + + defaultConfig { + versionName "20.47.14" + versionCode 204714000 + minSdkVersion androidMinSdk + targetSdkVersion androidTargetSdk + buildConfigField("String", "VERSION_NAME", "\"${defaultConfig.versionName}\"") + buildConfigField("int", "VERSION_CODE", "${defaultConfig.versionCode}") + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + lintOptions { + disable 'MissingTranslation' + } + + compileOptions { + sourceCompatibility = 1.8 + targetCompatibility = 1.8 + } + + kotlinOptions { + jvmTarget = 1.8 + } +} + +apply from: '../gradle/publish-android.gradle' + +description = 'microG service implementation for play-services-droidguard' diff --git a/play-services-droidguard-core/src/main/AndroidManifest.xml b/play-services-droidguard-core/src/main/AndroidManifest.xml new file mode 100644 index 00000000..c38b66c4 --- /dev/null +++ b/play-services-droidguard-core/src/main/AndroidManifest.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + diff --git a/play-services-droidguard-core/src/main/java/com/google/android/gms/droidguard/DroidGuardChimeraService.java b/play-services-droidguard-core/src/main/java/com/google/android/gms/droidguard/DroidGuardChimeraService.java new file mode 100644 index 00000000..a8a56582 --- /dev/null +++ b/play-services-droidguard-core/src/main/java/com/google/android/gms/droidguard/DroidGuardChimeraService.java @@ -0,0 +1,127 @@ +/* + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.droidguard; + +import android.content.Intent; +import android.os.Debug; +import android.os.Handler; +import android.os.IBinder; +import android.util.Base64; +import android.util.Log; + +import androidx.annotation.Nullable; + +import com.google.android.gms.framework.tracing.wrapper.TracingIntentService; + +import org.microg.gms.droidguard.DroidGuardServiceBroker; +import org.microg.gms.droidguard.GuardCallback; +import org.microg.gms.droidguard.HandleProxyFactory; +import org.microg.gms.droidguard.PingData; +import org.microg.gms.droidguard.Request; + +import java.util.Collections; +import java.util.concurrent.Executor; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +public class DroidGuardChimeraService extends TracingIntentService { + public static final Object a = new Object(); + // factory + public HandleProxyFactory b; + // widevine + public Object c; + // executor + public Executor d; + // log + public Object e; + + private static final Object f = new Object(); + + // ping + private Object g; + // handler + private Handler h; + + + public DroidGuardChimeraService() { + super("DG"); + setIntentRedelivery(true); + } + + public DroidGuardChimeraService(HandleProxyFactory factory, Object ping, Object database) { + super("DG"); + setIntentRedelivery(true); + this.b = factory; + this.g = ping; + this.h = new Handler(); + } + + // fsc + private final void c(byte[] data) { + PingData ping = null; + if (data != null) { + Log.d("GmsGuardChimera", "c(" + Base64.encodeToString(data, Base64.NO_WRAP) + ")", new RuntimeException().fillInStackTrace()); + try { + ping = PingData.ADAPTER.decode(data); + } catch (Exception e) { + Log.w("GmsGuardChimera", e); + } + } else { + Log.d("GmsGuardChimera", "c(null)", new RuntimeException().fillInStackTrace()); + } + byte[] bytes = b.createPingHandle(getPackageName(), "full", b(""), ping).run(Collections.emptyMap()); + Log.d("GmsGuardChimera", "c.bytes = " + Base64.encodeToString(bytes, Base64.NO_WRAP)); + Request fastRequest = b.createRequest("fast", getPackageName(), null, bytes); + b.fetchFromServer("fast", fastRequest); + } + + // handle intent + public final void a(@Nullable Intent intent) { + Log.d("GmsGuardChimera", "a(" + intent + ")"); + if (intent != null && intent.getAction() != null && intent.getAction().equals("com.google.android.gms.droidguard.service.PING")) { + byte[] byteData = intent.getByteArrayExtra("data"); + if (byteData == null) { + int[] intData = intent.getIntArrayExtra("data"); + if (intData == null) { + c(null); + return; + } + byteData = new byte[intData.length]; + for (int i = 0; i < intData.length; i++) { + byteData[i] = (byte) intData[i]; + } + } + c(byteData); + } + } + + // getCallback + public final GuardCallback b(String packageName) { + Log.d("GmsGuardChimera", "b[getCallback](" + packageName + ")"); + return new GuardCallback(this, packageName); + } + + @Nullable + @Override + public final IBinder onBind(Intent intent) { + if (intent != null && intent.getAction() != null && intent.getAction().equals("com.google.android.gms.droidguard.service.START")) { + return new DroidGuardServiceBroker(this); + } + return null; + } + + @Override + public void onCreate() { + this.e = new Object(); + this.b = new HandleProxyFactory(this); + this.g = new Object(); + this.h = new Handler(); + this.c = new Object(); + this.d = new ThreadPoolExecutor(1, 1, 0, TimeUnit.NANOSECONDS, new LinkedBlockingQueue<>(1), new ThreadPoolExecutor.DiscardPolicy()); + super.onCreate(); + } +} diff --git a/play-services-droidguard-core/src/main/java/com/google/android/gms/framework/tracing/wrapper/TracingIntentService.java b/play-services-droidguard-core/src/main/java/com/google/android/gms/framework/tracing/wrapper/TracingIntentService.java new file mode 100644 index 00000000..7d10c103 --- /dev/null +++ b/play-services-droidguard-core/src/main/java/com/google/android/gms/framework/tracing/wrapper/TracingIntentService.java @@ -0,0 +1,57 @@ +/* + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.framework.tracing.wrapper; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.google.android.chimera.IntentService; + +import org.microg.gms.utils.PackageManagerWrapper; +import org.microg.gms.droidguard.VersionUtil; + +public abstract class TracingIntentService extends IntentService { + private static final String TAG = "TracingIntentService"; + + public TracingIntentService(String name) { + super(name); + } + + @Override + public void attachBaseContext(Context newBase) { + super.attachBaseContext(newBase); + } + + protected abstract void a(@Nullable Intent intent); + + @Override + public PackageManager getPackageManager() { + return new PackageManagerWrapper(super.getPackageManager()) { + @NonNull + @Override + public PackageInfo getPackageInfo(@NonNull String packageName, int flags) { + PackageInfo packageInfo = super.getPackageInfo(packageName, flags); + if ("com.google.android.gms".equals(packageName)) { + VersionUtil versionUtil = new VersionUtil(TracingIntentService.this, new org.microg.gms.common.Build()); + packageInfo.versionCode = versionUtil.getVersionCode(); + packageInfo.versionName = versionUtil.getVersionString(); + packageInfo.sharedUserId = "com.google.uid.shared"; + } + return packageInfo; + } + }; + } + + @Override + public void onHandleIntent(@Nullable Intent intent) { + this.a(intent); + } +} diff --git a/play-services-droidguard-core/src/main/java/org/microg/gms/droidguard/GuardCallback.java b/play-services-droidguard-core/src/main/java/org/microg/gms/droidguard/GuardCallback.java new file mode 100644 index 00000000..4a3b4c2c --- /dev/null +++ b/play-services-droidguard-core/src/main/java/org/microg/gms/droidguard/GuardCallback.java @@ -0,0 +1,78 @@ +/* + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.droidguard; + +import android.content.Context; +import android.media.MediaDrm; +import android.os.Build; +import android.util.Log; + +import org.microg.gms.settings.SettingsContract; + +import java.util.HashMap; + +/** + * Callbacks invoked from the DroidGuard VM + *

+ * We keep this file in Java to ensure ABI compatibility. + * Methods are invoked by name from within the VM and thus must keep current name. + */ +public class GuardCallback { + private static final String TAG = "GmsGuardCallback"; + private final Context context; + private final String packageName; + + public GuardCallback(Context context, String packageName) { + this.context = context; + this.packageName = packageName; + } + + public final String a(final byte[] array) { + Log.d(TAG, "a[?](" + array + ")"); + return new String(FallbackCreator.create(new HashMap<>(), array, "", context, null)); + } + + // getAndroidId + public final String b() { + try { + long androidId = SettingsContract.INSTANCE.getSettings(context, SettingsContract.CheckIn.INSTANCE.getContentUri(context), new String[]{SettingsContract.CheckIn.ANDROID_ID}, cursor -> cursor.getLong(0)); + Log.d(TAG, "b[getAndroidId]() = " + androidId); + return String.valueOf(androidId); + } catch (Throwable e) { + Log.w(TAG, "Failed to get Android ID, fallback to random", e); + } + long androidId = (long) (Math.random() * Long.MAX_VALUE); + Log.d(TAG, "b[getAndroidId]() = " + androidId + " (random)"); + return String.valueOf(androidId); + } + + // getPackageName + public final String c() { + Log.d(TAG, "c[getPackageName]() = " + packageName); + return packageName; + } + + // closeMediaDrmSession + public final void d(final Object mediaDrm, final byte[] sessionId) { + Log.d(TAG, "d[closeMediaDrmSession](" + mediaDrm + ", " + sessionId + ")"); + synchronized (MediaDrmLock.LOCK) { + if (Build.VERSION.SDK_INT >= 18) { + ((MediaDrm) mediaDrm).closeSession(sessionId); + } + } + } + + public final void e(final int task) { + Log.d(TAG, "e[?](" + task + ")"); + // TODO: Open database + if (task == 1) { + // TODO + } else if (task == 0) { + // TODO + } + // TODO: Set value in database + } +} diff --git a/play-services-droidguard-core/src/main/java/org/microg/gms/droidguard/MediaDrmLock.java b/play-services-droidguard-core/src/main/java/org/microg/gms/droidguard/MediaDrmLock.java new file mode 100644 index 00000000..4914cb78 --- /dev/null +++ b/play-services-droidguard-core/src/main/java/org/microg/gms/droidguard/MediaDrmLock.java @@ -0,0 +1,10 @@ +/* + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.droidguard; + +public class MediaDrmLock { + public static final Object LOCK = new Object(); +} diff --git a/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/BytesException.kt b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/BytesException.kt new file mode 100644 index 00000000..32721ee2 --- /dev/null +++ b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/BytesException.kt @@ -0,0 +1,22 @@ +/* + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.droidguard + +class BytesException : Exception { + val bytes: ByteArray + + constructor(bytes: ByteArray, message: String) : super(message) { + this.bytes = bytes + } + + constructor(bytes: ByteArray, cause: Throwable) : super(cause) { + this.bytes = bytes + } + + constructor(bytes: ByteArray, message: String, cause: Throwable) : super(message, cause) { + this.bytes = bytes + } +} diff --git a/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DgDatabaseHelper.kt b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DgDatabaseHelper.kt new file mode 100644 index 00000000..e1c4f491 --- /dev/null +++ b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DgDatabaseHelper.kt @@ -0,0 +1,68 @@ +/* + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.droidguard + +import android.content.ContentValues +import android.content.Context +import android.database.sqlite.SQLiteDatabase +import android.database.sqlite.SQLiteOpenHelper + +/** + * - a: id + * - b: timestamp + * - c: seconds until expiry + * - d: vm key + * - e: ? + * - f: byte code + * - g: extra + */ +class DgDatabaseHelper(context: Context) : SQLiteOpenHelper(context, "dg.db", null, 2) { + override fun onCreate(db: SQLiteDatabase) { + // Note: "NON NULL" is actually not a valid sqlite constraint, but this is what we see in the original database 🤷 + db.execSQL("CREATE TABLE main (a TEXT NOT NULL, b LONG NOT NULL, c LONG NOT NULL, d TEXT NON NULL, e TEXT NON NULL,f BLOB NOT NULL,g BLOB NOT NULL);"); + } + + override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { + db.execSQL("DROP TABLE IF EXISTS main;"); + this.onCreate(db); + } + + /** + * @return vm key, byte code, extra + */ + fun get(id: String): Triple? = readableDatabase.use { db -> + val time = System.currentTimeMillis() / 1000 + db.query("main", arrayOf("f", "d", "e", "c", "g"), "a = ? AND b <= $time AND $time < (b + c)", arrayOf(id), null, null, "b DESC", "1").use { + if (it.moveToNext()) { + Triple(it.getString(1), it.getBlob(0), it.getBlob(4)) + } else { + null + } + } + } + + fun put(id: String, expiry: Long, vmKey: String, byteCode: ByteArray, extra: ByteArray) { + val dbData = ContentValues().apply { + put("a", id) + put("b", System.currentTimeMillis() / 1000) + put("c", expiry) + put("d", vmKey) + put("e", "") + put("f", byteCode) + put("g", extra) + } + writableDatabase.use { + it.beginTransaction() + if (expiry <= 0) { + it.delete("main", "a = ?", arrayOf(id)) + } else if (it.update("main", dbData, "a = ?", arrayOf(id)) <= 0) { + it.insert("main", null, dbData) + } + it.setTransactionSuccessful() + it.endTransaction() + } + } +} diff --git a/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DgpDatabaseHelper.kt b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DgpDatabaseHelper.kt new file mode 100644 index 00000000..269de569 --- /dev/null +++ b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DgpDatabaseHelper.kt @@ -0,0 +1,19 @@ +/* + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.droidguard + +import android.content.Context +import android.database.sqlite.SQLiteDatabase +import android.database.sqlite.SQLiteOpenHelper + +class DgpDatabaseHelper(context: Context) : SQLiteOpenHelper(context, "dgp.db", null, 1) { + override fun onCreate(db: SQLiteDatabase) { + db.execSQL("CREATE TABLE t (a BLOB NOT NULL);"); + } + + override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { + } +} diff --git a/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardHandleImpl.kt b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardHandleImpl.kt new file mode 100644 index 00000000..fff942c1 --- /dev/null +++ b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardHandleImpl.kt @@ -0,0 +1,112 @@ +/* + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.droidguard + +import android.annotation.SuppressLint +import android.content.Context +import android.os.ConditionVariable +import android.os.ParcelFileDescriptor +import android.os.Parcelable +import android.util.Log +import com.google.android.gms.droidguard.internal.DroidGuardInitReply +import com.google.android.gms.droidguard.internal.DroidGuardResultsRequest +import com.google.android.gms.droidguard.internal.IDroidGuardHandle +import java.io.FileNotFoundException + +class DroidGuardHandleImpl(private val context: Context, private val packageName: String, private val factory: HandleProxyFactory, private val callback: GuardCallback) : IDroidGuardHandle.Stub() { + private val condition = ConditionVariable() + + private var flow: String? = null + private var handleProxy: HandleProxy? = null + private var handleInitError: Throwable? = null + + override fun init(flow: String?) { + Log.d(TAG, "init($flow)") + initWithRequest(flow, null) + } + + @SuppressLint("SetWorldReadable") + override fun initWithRequest(flow: String?, request: DroidGuardResultsRequest?): DroidGuardInitReply { + Log.d(TAG, "initWithRequest($flow)") + this.flow = flow + try { + var handleProxy: HandleProxy? = null + if (flow !in NOT_LOW_LATENCY_FLOWS) { + try { + handleProxy = factory.createLowLatencyHandle(flow, callback, request) + Log.d(TAG, "Using low-latency handle") + } catch (e: Exception) { + Log.w(TAG, e) + } + } + if (handleProxy == null) { + handleProxy = factory.createHandle(packageName, flow, callback, request) + } + if (handleProxy.init()) { + this.handleProxy = handleProxy + } else { + throw Exception("init failed") + } + } catch (e: Throwable) { + Log.w(TAG, "Error during handle init", e) + handleInitError = e + } + condition.open() + if (handleInitError == null) { + try { + handleProxy?.let { handleProxy -> + val `object` = handleProxy.handle.javaClass.getDeclaredMethod("rb").invoke(handleProxy.handle) as? Parcelable? + if (`object` != null) { + val vmKey = handleProxy.vmKey + val theApk = factory.getTheApkFile(vmKey) + try { + theApk.setReadable(true, false) + return DroidGuardInitReply(ParcelFileDescriptor.open(theApk, ParcelFileDescriptor.MODE_READ_ONLY), `object`) + } catch (e: FileNotFoundException) { + throw Exception("Files for VM $vmKey not found on disk") + } + } + } + } catch (e: Exception) { + this.handleProxy = null + handleInitError = e + } + } + return DroidGuardInitReply(null, null) + } + + override fun guard(map: MutableMap): ByteArray { + Log.d(TAG, "guard()") + handleInitError?.let { return FallbackCreator.create(flow, context, map, it) } + val handleProxy = this.handleProxy ?: return FallbackCreator.create(flow, context, map, IllegalStateException()) + return try { + handleProxy.handle::class.java.getDeclaredMethod("ss", Map::class.java).invoke(handleProxy.handle, map) as ByteArray + } catch (e: Exception) { + try { + throw BytesException(handleProxy.extra, e) + } catch (e2: Exception) { + FallbackCreator.create(flow, context, map, e2) + } + } + } + + override fun close() { + Log.d(TAG, "close()") + condition.block() + try { + handleProxy?.close() + } catch (e: Exception) { + Log.w(TAG, "Error during handle close", e) + } + handleProxy = null + handleInitError = null + } + + companion object { + private const val TAG = "GmsGuardHandleImpl" + private val NOT_LOW_LATENCY_FLOWS = setOf("ad_attest", "attest", "checkin", "federatedMachineLearningReduced", "msa-f", "ad-event-attest-token") + } +} diff --git a/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardPreferences.kt b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardPreferences.kt new file mode 100644 index 00000000..d038c53c --- /dev/null +++ b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardPreferences.kt @@ -0,0 +1,39 @@ +/* + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.droidguard + +import android.content.Context +import android.database.Cursor +import androidx.core.database.getStringOrNull +import org.microg.gms.settings.SettingsContract +import org.microg.gms.settings.SettingsContract.DroidGuard.ENABLED +import org.microg.gms.settings.SettingsContract.DroidGuard.MODE +import org.microg.gms.settings.SettingsContract.DroidGuard.NETWORK_SERVER_URL + +object DroidGuardPreferences { + + private fun getSettings(context: Context, projection: String, def: T, f: (Cursor) -> T): T { + return try { + SettingsContract.getSettings(context, SettingsContract.DroidGuard.getContentUri(context), arrayOf(projection), f) + } catch (e: Exception) { + def + } + } + + @JvmStatic + fun isEnabled(context: Context): Boolean = true //getSettings(context, ENABLED, false) { it.getInt(0) != 0 } + + @JvmStatic + fun getMode(context: Context): Mode = getSettings(context, MODE, Mode.Embedded) { c -> Mode.valueOf(c.getString(0)) } + + @JvmStatic + fun getNetworkServerUrl(context: Context): String? = getSettings(context, NETWORK_SERVER_URL, null) { c -> c.getStringOrNull(0) } + + enum class Mode { + Embedded, + Network + } +} diff --git a/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardResultCreator.kt b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardResultCreator.kt new file mode 100644 index 00000000..68e2bee3 --- /dev/null +++ b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardResultCreator.kt @@ -0,0 +1,72 @@ +/* + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.droidguard + +import android.content.Context +import android.util.Base64 +import com.android.volley.VolleyError +import com.android.volley.toolbox.StringRequest +import com.android.volley.toolbox.Volley +import com.google.android.gms.tasks.await +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException +import kotlin.coroutines.suspendCoroutine + +interface DroidGuardResultCreator { + suspend fun getResult(flow: String, data: Map): ByteArray + + companion object { + fun getInstance(context: Context): DroidGuardResultCreator = + if (DroidGuardPreferences.isEnabled(context)) { + when (DroidGuardPreferences.getMode(context)) { + DroidGuardPreferences.Mode.Embedded -> EmbeddedDroidGuardResultCreator(context) + DroidGuardPreferences.Mode.Network -> NetworkDroidGuardResultCreator(context) + } + } else { + throw RuntimeException("DroidGuard disabled") + } + + suspend fun getResult(context: Context, flow: String, data: Map): ByteArray = + getInstance(context).getResult(flow, data) + } +} + +private class NetworkDroidGuardResultCreator(private val context: Context) : DroidGuardResultCreator { + private val queue = Volley.newRequestQueue(context) + private val url: String + get() = DroidGuardPreferences.getNetworkServerUrl(context) ?: throw RuntimeException("Network URL required") + + override suspend fun getResult(flow: String, data: Map): ByteArray = suspendCoroutine { continuation -> + queue.add(PostParamsStringRequest("$url?flow=$flow", data, { + continuation.resume(Base64.decode(it, Base64.NO_WRAP + Base64.NO_PADDING + Base64.URL_SAFE)) + }, { + continuation.resumeWithException(RuntimeException(it)) + })) + } + + companion object { + class PostParamsStringRequest(url: String, private val data: Map, listener: (String) -> Unit, errorListener: (VolleyError) -> Unit) : StringRequest(Method.POST, url, listener, errorListener) { + override fun getParams(): Map = data + } + } +} + +private class EmbeddedDroidGuardResultCreator(private val context: Context) : DroidGuardResultCreator { + private val client: DroidGuardClient by lazy { DroidGuardClientImpl(context) } + override suspend fun getResult(flow: String, data: Map): ByteArray { + val handle = client.getHandle().await() + try { + handle.init(flow) + return handle.guard(data) + } finally { + try { + handle.close() + } catch (e: Exception) { + // ignore + } + } + } +} diff --git a/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardService.kt b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardService.kt new file mode 100644 index 00000000..e68359c6 --- /dev/null +++ b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardService.kt @@ -0,0 +1,11 @@ +/* + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ +package org.microg.gms.droidguard + +import com.google.android.gms.droidguard.DroidGuardChimeraService +import org.microg.gms.chimera.ServiceLoader +import org.microg.gms.chimera.ServiceProxy + +class DroidGuardService : ServiceProxy(ServiceLoader.static()) diff --git a/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardServiceBroker.kt b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardServiceBroker.kt new file mode 100644 index 00000000..37dd4724 --- /dev/null +++ b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardServiceBroker.kt @@ -0,0 +1,26 @@ +/* + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.droidguard + +import com.google.android.gms.common.internal.GetServiceRequest +import com.google.android.gms.common.internal.IGmsCallbacks +import com.google.android.gms.droidguard.DroidGuardChimeraService +import org.microg.gms.AbstractGmsServiceBroker +import org.microg.gms.common.GmsService +import org.microg.gms.common.PackageUtils +import java.util.* + +class DroidGuardServiceBroker(val service: DroidGuardChimeraService) : AbstractGmsServiceBroker(EnumSet.of(GmsService.DROIDGUARD)) { + + override fun getService(callback: IGmsCallbacks?, request: GetServiceRequest?) { + handleServiceRequest(callback, request, null) + } + + override fun handleServiceRequest(callback: IGmsCallbacks?, request: GetServiceRequest?, service: GmsService?) { + val packageName = PackageUtils.getAndCheckCallingPackageOrExtendedAccess(this.service, request!!.packageName) + callback!!.onPostInitComplete(0, DroidGuardServiceImpl(this.service, packageName!!), null) + } +} diff --git a/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardServiceImpl.kt b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardServiceImpl.kt new file mode 100644 index 00000000..b05b170e --- /dev/null +++ b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardServiceImpl.kt @@ -0,0 +1,39 @@ +/* + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.droidguard + +import android.util.Log +import com.google.android.gms.droidguard.DroidGuardChimeraService +import com.google.android.gms.droidguard.internal.DroidGuardResultsRequest +import com.google.android.gms.droidguard.internal.IDroidGuardCallbacks +import com.google.android.gms.droidguard.internal.IDroidGuardHandle +import com.google.android.gms.droidguard.internal.IDroidGuardService + +class DroidGuardServiceImpl(private val service: DroidGuardChimeraService, private val packageName: String) : IDroidGuardService.Stub() { + override fun guard(callbacks: IDroidGuardCallbacks?, flow: String?, map: MutableMap?) { + Log.d(TAG, "guard()") + guardWithRequest(callbacks, flow, map, null) + } + + override fun guardWithRequest(callbacks: IDroidGuardCallbacks?, flow: String?, map: MutableMap?, request: DroidGuardResultsRequest?) { + Log.d(TAG, "guardWithRequest()") + TODO("Not yet implemented") + } + + override fun getHandle(): IDroidGuardHandle { + Log.d(TAG, "getHandle()") + return DroidGuardHandleImpl(service, packageName, service.b, service.b(packageName)) + } + + override fun getClientTimeoutMillis(): Int { + Log.d(TAG, "getClientTimeoutMillis()") + return 60000 + } + + companion object { + const val TAG = "GmsGuardServiceImpl" + } +} diff --git a/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/FallbackCreator.kt b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/FallbackCreator.kt new file mode 100644 index 00000000..fb648c2f --- /dev/null +++ b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/FallbackCreator.kt @@ -0,0 +1,33 @@ +/* + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.droidguard + +import android.content.Context +import android.util.Log + +object FallbackCreator { + private val FAST_FAIL = setOf("ad_attest", "recaptcha-frame", "federatedMachineLearningReduced", "msa-f", "ad-event-attest-token") + + @JvmStatic + fun create(flow: String?, context: Context, map: Map, e: Throwable): ByteArray { + Log.w("DGFallback", "create($flow)") + return if (flow in FAST_FAIL) { + "ERROR : no fallback for $flow".encodeToByteArray() + } else { + try { + create(map, null, flow, context, e) + } catch (e: Throwable) { + Log.w("DGFallback", e) + "ERROR : $e".encodeToByteArray() + } + } + } + + @JvmStatic + fun create(map: Map, bytes: ByteArray?, flow: String?, context: Context, e: Throwable): ByteArray { + TODO("Not yet implemented") + } +} diff --git a/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/HandleProxy.kt b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/HandleProxy.kt new file mode 100644 index 00000000..e1147996 --- /dev/null +++ b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/HandleProxy.kt @@ -0,0 +1,53 @@ +/* + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.droidguard + +import android.content.Context +import android.os.Bundle +import android.os.Parcelable + +class HandleProxy(val handle: Any, val vmKey: String, val extra: ByteArray = ByteArray(0)) { + constructor(clazz: Class<*>, context: Context, vmKey: String, data: Parcelable) : this( + kotlin.runCatching { + clazz.getDeclaredConstructor(Context::class.java, Parcelable::class.java).newInstance(context, data) + }.getOrElse { + throw BytesException(ByteArray(0), it) + }, + vmKey + ) + + constructor(clazz: Class<*>, context: Context, flow: String?, byteCode: ByteArray, callback: Any, vmKey: String, extra: ByteArray, bundle: Bundle?) : this( + kotlin.runCatching { + clazz.getDeclaredConstructor(Context::class.java, String::class.java, ByteArray::class.java, Object::class.java, Bundle::class.java).newInstance(context, flow, byteCode, callback, bundle) + }.getOrElse { + throw BytesException(extra, it) + }, vmKey, extra) + + fun run(data: Map): ByteArray { + try { + return handle.javaClass.getDeclaredMethod("run", Map::class.java).invoke(handle, data) as ByteArray + } catch (e: Exception) { + throw BytesException(extra, e) + } + } + + fun init(): Boolean { + try { + return handle.javaClass.getDeclaredMethod("init").invoke(handle) as Boolean + } catch (e: Exception) { + throw BytesException(extra, e) + } + } + + fun close() { + try { + handle.javaClass.getDeclaredMethod("close").invoke(handle) + } catch (e: Exception) { + throw BytesException(extra, e) + } + } + +} diff --git a/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/HandleProxyFactory.kt b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/HandleProxyFactory.kt new file mode 100644 index 00000000..a8fe931b --- /dev/null +++ b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/HandleProxyFactory.kt @@ -0,0 +1,215 @@ +/* + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.droidguard + +import android.content.Context +import com.android.volley.NetworkResponse +import com.android.volley.VolleyError +import com.android.volley.toolbox.RequestFuture +import com.android.volley.toolbox.Volley +import com.google.android.gms.droidguard.internal.DroidGuardResultsRequest +import dalvik.system.DexClassLoader +import okio.ByteString.Companion.decodeHex +import okio.ByteString.Companion.of +import org.microg.gms.common.Build +import org.microg.gms.droidguard.core.BuildConfig +import java.io.File +import java.io.IOException +import java.security.MessageDigest +import java.security.cert.Certificate +import java.util.* +import com.android.volley.Request as VolleyRequest +import com.android.volley.Response as VolleyResponse + +class HandleProxyFactory(private val context: Context) { + private val build: Build = Build() + private val classMap = hashMapOf>() + private val dgDb: DgDatabaseHelper = DgDatabaseHelper(context) + private val version = VersionUtil(context, build) + private val queue = Volley.newRequestQueue(context) + + fun createHandle(packageName: String, flow: String?, callback: GuardCallback, request: DroidGuardResultsRequest?): HandleProxy { + val (vmKey, byteCode, bytes) = readFromDatabase(flow) ?: fetchFromServer(flow, packageName) + return createHandleProxy(flow, vmKey, byteCode, bytes, callback, request) + } + + fun createPingHandle(packageName: String, flow: String, callback: GuardCallback, pingData: PingData?): HandleProxy { + val (vmKey, byteCode, bytes) = fetchFromServer(flow, createRequest(flow, packageName, pingData)) + return createHandleProxy(flow, vmKey, byteCode, bytes, callback, DroidGuardResultsRequest().also { it.clientVersion = 0 }) + } + + fun createLowLatencyHandle(flow: String?, callback: GuardCallback, request: DroidGuardResultsRequest?): HandleProxy { + val (vmKey, byteCode, bytes) = readFromDatabase("fast") ?: throw Exception("low latency (fast) flow not available") + return createHandleProxy(flow, vmKey, byteCode, bytes, callback, request) + } + + fun SignedResponse.unpack(): Response { + if (SignatureVerifier.verifySignature(data!!.toByteArray(), signature!!.toByteArray())) { + return Response.ADAPTER.decode(data!!) + } else { + throw SecurityException("Signature invalid") + } + } + + private fun readFromDatabase(flow: String?): Triple? { + val id = "$flow/${version.versionString}/${build.fingerprint}" + return dgDb.get(id) + } + + fun createRequest(flow: String?, packageName: String, pingData: PingData? = null, extra: ByteArray? = null): Request { + return Request( + usage = Usage(flow, packageName), + info = listOf( + KeyValuePair("BOARD", build.board), + KeyValuePair("BOOTLOADER", build.bootloader), + KeyValuePair("BRAND", build.brand), + KeyValuePair("CPU_ABI", build.cpu_abi), + KeyValuePair("CPU_ABI2", build.cpu_abi2), + KeyValuePair("SUPPORTED_ABIS", build.supported_abis.joinToString(",")), + KeyValuePair("DEVICE", build.device), + KeyValuePair("DISPLAY", build.display), + KeyValuePair("FINGERPRINT", build.fingerprint), + KeyValuePair("HARDWARE", build.hardware), + KeyValuePair("HOST", build.host), + KeyValuePair("ID", build.id), + KeyValuePair("MANUFACTURER", build.manufacturer), + KeyValuePair("MODEL", build.model), + KeyValuePair("PRODUCT", build.product), + KeyValuePair("RADIO", build.radio), + KeyValuePair("SERIAL", build.serial), + KeyValuePair("TAGS", build.tags), + KeyValuePair("TIME", build.time.toString()), + KeyValuePair("TYPE", build.type), + KeyValuePair("USER", build.user), + KeyValuePair("VERSION.CODENAME", build.version_codename), + KeyValuePair("VERSION.INCREMENTAL", build.version_incremental), + KeyValuePair("VERSION.RELEASE", build.version_release), + KeyValuePair("VERSION.SDK", build.version_sdk), + KeyValuePair("VERSION.SDK_INT", build.version_sdk_int.toString()), + ), + versionName = version.versionString, + versionCode = BuildConfig.VERSION_CODE, + hasAccount = false, + isGoogleCn = false, + enableInlineVm = true, + cached = getCacheDir().list()?.map { it.decodeHex() }.orEmpty(), + arch = System.getProperty("os.arch"), + ping = pingData, + field10 = extra?.let { of(*it) }, + ) + } + + fun fetchFromServer(flow: String?, packageName: String): Triple { + return fetchFromServer(flow, createRequest(flow, packageName)) + } + + fun fetchFromServer(flow: String?, request: Request): Triple { + val future = RequestFuture.newFuture() + queue.add(object : VolleyRequest(Method.POST, SERVER_URL, future) { + override fun parseNetworkResponse(response: NetworkResponse): VolleyResponse { + return try { + VolleyResponse.success(SignedResponse.ADAPTER.decode(response.data), null) + } catch (e: Exception) { + VolleyResponse.error(VolleyError(e)) + } + } + + override fun deliverResponse(response: SignedResponse) { + future.onResponse(response) + } + + override fun getBody(): ByteArray = request.encode() + + override fun getBodyContentType(): String = "application/x-protobuf" + + override fun getHeaders(): Map { + return mapOf( + "User-Agent" to "DroidGuard/${version.versionCode}" + ) + } + }) + val signed: SignedResponse = future.get() + val response = signed.unpack() + val vmKey = response.vmChecksum!!.hex() + if (!isValidCache(vmKey)) { + val temp = File(getCacheDir(), "${UUID.randomUUID()}.apk") + temp.parentFile!!.mkdirs() + temp.writeBytes(response.content!!.toByteArray()) + getOptDir(vmKey).mkdirs() + temp.renameTo(getTheApkFile(vmKey)) + updateCacheTimestamp(vmKey) + if (!isValidCache(vmKey)) { + getCacheDir(vmKey).deleteRecursively() + throw IllegalStateException() + } + } + val id = "$flow/${version.versionString}/${build.fingerprint}" + val expiry = (response.expiryTimeSecs ?: 0).toLong() + val byteCode = response.byteCode!!.toByteArray() + val extra = response.extra!!.toByteArray() + dgDb.put(id, expiry, vmKey, byteCode, extra) + return Triple(vmKey, byteCode, extra) + } + + private fun createHandleProxy(flow: String?, vmKey: String, byteCode: ByteArray, extra: ByteArray, callback: GuardCallback, request: DroidGuardResultsRequest?): HandleProxy { + val clazz = loadClass(vmKey, extra) + return HandleProxy(clazz, context, flow, byteCode, callback, vmKey, extra, request?.bundle) + } + + fun getTheApkFile(vmKey: String) = File(getCacheDir(vmKey), "the.apk") + private fun getCacheDir() = context.getDir(CACHE_FOLDER_NAME, Context.MODE_PRIVATE) + private fun getCacheDir(vmKey: String) = File(getCacheDir(), vmKey) + private fun getOptDir(vmKey: String) = File(getCacheDir(vmKey), "opt") + private fun isValidCache(vmKey: String) = getTheApkFile(vmKey).isFile && getOptDir(vmKey).isDirectory + + private fun updateCacheTimestamp(vmKey: String) { + try { + val timestampFile = File(getCacheDir(vmKey), "t") + if (!timestampFile.exists() && !timestampFile.createNewFile()) { + throw Exception("Failed to touch last-used file for $vmKey.") + } + if (!timestampFile.setLastModified(System.currentTimeMillis())) { + throw Exception("Failed to update last-used timestamp for $vmKey.") + } + } catch (e: IOException) { + throw Exception("Failed to touch last-used file for $vmKey.") + } + } + + private fun verifyApkSignature(apk: File): Boolean { + return true + val certificates: Array = TODO() + if (certificates.size != 1) return false + return Arrays.equals(MessageDigest.getInstance("SHA-256").digest(certificates[0].encoded), PROD_CERT_HASH) + } + + private fun loadClass(vmKey: String, bytes: ByteArray): Class<*> { + val clazz = classMap[vmKey] + if (clazz != null) { + updateCacheTimestamp(vmKey) + return clazz + } else { + if (!isValidCache(vmKey)) { + throw BytesException(bytes, "VM key $vmKey not found in cache") + } + if (!verifyApkSignature(getTheApkFile(vmKey))) { + getCacheDir(vmKey).deleteRecursively() + throw ClassNotFoundException("APK signature verification failed") + } + val loader = DexClassLoader(getTheApkFile(vmKey).absolutePath, getOptDir(vmKey).absolutePath, null, context.classLoader) + val clazz = loader.loadClass(CLASS_NAME) + classMap[vmKey] = clazz + return clazz + } + } + + companion object { + const val CLASS_NAME = "com.google.ccc.abuse.droidguard.DroidGuard" + const val SERVER_URL = "https://www.googleapis.com/androidantiabuse/v1/x/create?alt=PROTO&key=AIzaSyBofcZsgLSS7BOnBjZPEkk4rYwzOIz-lTI" + const val CACHE_FOLDER_NAME = "cache_dg" + val PROD_CERT_HASH = byteArrayOf(61, 122, 18, 35, 1, -102, -93, -99, -98, -96, -29, 67, 106, -73, -64, -119, 107, -5, 79, -74, 121, -12, -34, 95, -25, -62, 63, 50, 108, -113, -103, 74) + } +} diff --git a/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/SignatureVerifier.kt b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/SignatureVerifier.kt new file mode 100644 index 00000000..5c8b8460 --- /dev/null +++ b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/SignatureVerifier.kt @@ -0,0 +1,31 @@ +/* + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.droidguard + +import android.util.Base64 +import android.util.Log +import java.security.KeyFactory +import java.security.Signature +import java.security.spec.X509EncodedKeySpec + +object SignatureVerifier { + const val TAG = "GmsGuardSigVerify" + const val PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxW77dCKJ8mhEIfXXdeidi7/7LMNM/fzwI+wj1Ed8xIKgTYWCnekRko3JxQb4Cv/gEL5hEA8e9lFs3V67VUL6hCo1FxysXj7Q8n3Kp7hARDkbiZ0mdk8bSanqrPAXTPx6pEL2ZOzfFCHEtJdhz5Ozp2C4XTKF1SBv/YbpsqSUJwdhG7ZPGjyCMRloMww6ITpGdVQ8lChklkCek0WPbz2UrY5RC1qIJKmmcB6KNxxE776Dn6QoYbhN5jPeVBp7lDD3UxjfVzTxKKDAome6fUVBop3dpcLM6rq3+nNT2YArgqTD1qtsVM9vHlcLaAYaPg82vtIN80iDUseMlVHgK+nf6wIDAQAB" + + fun verifySignature(data: ByteArray, signature: ByteArray): Boolean { + try { + val keyFactory = KeyFactory.getInstance("RSA") ?: return false + val sig = Signature.getInstance("SHA256withRSA") ?: return false + val keySpec = X509EncodedKeySpec(Base64.decode(PUBLIC_KEY, 0)) + sig.initVerify(keyFactory.generatePublic(keySpec)) + sig.update(data) + return sig.verify(signature) + } catch (e: Exception) { + Log.w(TAG, e) + return false + } + } +} diff --git a/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/VersionUtil.kt b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/VersionUtil.kt new file mode 100644 index 00000000..c9a4b35f --- /dev/null +++ b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/VersionUtil.kt @@ -0,0 +1,75 @@ +/* + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.droidguard + +import android.content.Context +import org.microg.gms.common.Build +import org.microg.gms.droidguard.core.BuildConfig + +class VersionUtil(private val context: Context, private val build: Build = Build()) { + val buildType: String + get() { + // Note: Android TV and Watch use different version codes + val versionCode = when (build.version_sdk_int) { + 31 -> "19" + 30 -> "15" + 29 -> "12" + 28 -> "10" + 23, 24, 25, 26, 27 -> "04" + 21, 22 -> "02" + else -> "00" + } + val architectureCode = when (build.cpu_abi) { + "x86_64" -> "08" + "x86" -> "07" + "arm64-v8a" -> "04" + "arm", "armeabi", "armeabi-v7a" -> "03" + else -> "00" + } + val dpiCode = when (context.resources.displayMetrics.densityDpi) { + 160 -> "02" + 240 -> "04" + 320 -> "06" + 480 -> "08" + else -> "00" + } + val type = "$versionCode$architectureCode$dpiCode" + if (isKnown(type)) return type + val nodpi = "$versionCode${architectureCode}00" + if (isKnown(nodpi)) return nodpi // Fallback to nodpi for increased compat + return type // Use unknown build type + } + val versionString: String + get() = "${BuildConfig.VERSION_NAME} ($buildType-{{cl}})" + val versionCode: Int + get() = BuildConfig.VERSION_CODE + (getVersionOffset(buildType) ?: 0) + + fun isKnown(type: String): Boolean = getVersionOffset(type) != null + + fun getVersionOffset(type: String): Int? { + val v1 = type.substring(0, 2) + val v2 = type.substring(2, 4) + val v3 = type.substring(4, 6) + val i1 = BUILD_MAP.indexOfFirst { it.first == v1 }.takeIf { it >= 0 } ?: return null + val i2 = BUILD_MAP[i1].second.indexOfFirst { it.first == v2 }.takeIf { it >= 0 } ?: return null + val i3 = BUILD_MAP[i1].second[i2].second.indexOf(v3).takeIf { it > 0 } ?: return null + val o1 = BUILD_MAP.subList(0, i1).map { it.second.map { it.second.size }.sum() }.sum() + val o2 = BUILD_MAP[i1].second.subList(0, i2).map { it.second.size }.sum() + return o1 + o2 + i3 + } + + companion object { + val BUILD_MAP = listOf( + "00" to listOf("03" to listOf("00", "02", "04", "06", "08"), "07" to listOf("00")), + "02" to listOf("03" to listOf("00", "04", "06", "08"), "04" to listOf("00", "06", "08"), "07" to listOf("00"), "08" to listOf("00")), + "04" to listOf("03" to listOf("00", "04", "06", "08"), "04" to listOf("00", "06", "08"), "07" to listOf("00"), "08" to listOf("00")), + "10" to listOf("03" to listOf("00", "04", "06", "08"), "04" to listOf("00", "06", "08"), "07" to listOf("00"), "08" to listOf("00")), + "12" to listOf("03" to listOf("00", "04", "06", "08"), "04" to listOf("00", "06", "08"), "07" to listOf("00"), "08" to listOf("00")), + "15" to listOf("03" to listOf("00", "04", "06", "08"), "04" to listOf("00", "06", "08"), "07" to listOf("00"), "08" to listOf("00")), + "19" to listOf("03" to listOf("00", "08"), "04" to listOf("00", "08"), "07" to listOf("00"), "08" to listOf("00")), + ) + } +} diff --git a/play-services-droidguard/src/main/kotlin/org/microg/gms/droidguard/DroidGuardClient.kt b/play-services-droidguard/src/main/kotlin/org/microg/gms/droidguard/DroidGuardClient.kt index adcaa45b..a5b8f3d4 100644 --- a/play-services-droidguard/src/main/kotlin/org/microg/gms/droidguard/DroidGuardClient.kt +++ b/play-services-droidguard/src/main/kotlin/org/microg/gms/droidguard/DroidGuardClient.kt @@ -5,7 +5,6 @@ package org.microg.gms.droidguard -import com.google.android.gms.droidguard.internal.IDroidGuardHandle import com.google.android.gms.tasks.Task interface DroidGuardClient { diff --git a/play-services-droidguard/src/main/kotlin/org/microg/gms/droidguard/DroidGuardClientImpl.kt b/play-services-droidguard/src/main/kotlin/org/microg/gms/droidguard/DroidGuardClientImpl.kt index 49f3fc63..d8af0b6d 100644 --- a/play-services-droidguard/src/main/kotlin/org/microg/gms/droidguard/DroidGuardClientImpl.kt +++ b/play-services-droidguard/src/main/kotlin/org/microg/gms/droidguard/DroidGuardClientImpl.kt @@ -11,14 +11,13 @@ import com.google.android.gms.common.api.Api import com.google.android.gms.common.api.Api.ApiOptions.NoOptions import com.google.android.gms.common.api.GoogleApi import com.google.android.gms.tasks.Task -import org.microg.gms.common.api.ApiClientBuilder import org.microg.gms.common.api.ApiClientSettings import org.microg.gms.common.api.ConnectionCallbacks import org.microg.gms.common.api.OnConnectionFailedListener class DroidGuardClientImpl(context: Context) : GoogleApi(context, API), DroidGuardClient { companion object { - private val API = Api(ApiClientBuilder { _: NoOptions?, context: Context, _: Looper?, _: ApiClientSettings?, callbacks: ConnectionCallbacks, connectionFailedListener: OnConnectionFailedListener -> DroidGuardApiClient(context, callbacks, connectionFailedListener) }) + private val API = Api { _: NoOptions?, context: Context, _: Looper?, _: ApiClientSettings?, callbacks: ConnectionCallbacks, connectionFailedListener: OnConnectionFailedListener -> DroidGuardApiClient(context, callbacks, connectionFailedListener) } } override fun getHandle(): Task { diff --git a/settings.gradle b/settings.gradle index bda5a7e1..601c7f71 100644 --- a/settings.gradle +++ b/settings.gradle @@ -36,6 +36,7 @@ include ':firebase-dynamic-links-api' // core only include ':play-services-core-proto' +include ':play-services-droidguard-core-proto' include ':play-services-nearby-core-proto' include ':play-services-wearable-proto' @@ -46,6 +47,7 @@ include ':play-services-base-core' include ':play-services-chimera-core' include ':play-services-conscrypt-provider-core' include ':play-services-cronet-core' +include ':play-services-droidguard-core' include ':play-services-location-core' include ':play-services-maps-core-mapbox' include ':play-services-maps-core-vtm'