Add DroidGuard support

This commit is contained in:
Marvin W 2021-10-07 10:20:42 +02:00
parent d8325870cb
commit 6d45bfb7ed
No known key found for this signature in database
GPG Key ID: 072E9235DB996F2A
41 changed files with 1970 additions and 327 deletions

View File

@ -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'

View File

@ -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";

View File

@ -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);

View File

@ -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 <T> withoutCallingIdentity(f: () -> T): T {
@ -118,6 +140,7 @@ object SettingsContract {
}
}
@JvmStatic
fun <T> getSettings(context: Context, uri: Uri, projection: Array<out String>?, 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)

View File

@ -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<out String>): 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<out String>): 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<out String>,
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<SharedPreferences?>.getString(key: String, def: String?): String? = foldRight(def) { preferences, defValue -> preferences?.getString(key, defValue) ?: defValue }
private fun List<SharedPreferences?>.getInt(key: String, def: Int): Int = foldRight(def) { preferences, defValue -> preferences?.getInt(key, defValue) ?: defValue }
private fun List<SharedPreferences?>.getLong(key: String, def: Long): Long = foldRight(def) { preferences, defValue -> preferences?.getLong(key, defValue) ?: defValue }
private fun List<SharedPreferences?>.getBoolean(key: String, def: Boolean): Boolean = foldRight(def) { preferences, defValue -> preferences?.getBoolean(key, defValue) ?: defValue }
private fun List<SharedPreferences?>.getBooleanAsInt(key: String, def: Boolean): Int = if (getBoolean(key, def)) 1 else 0
}

View File

@ -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<out String>): Array<String> {
return wrapped.currentToCanonicalPackageNames(packageNames)
}
override fun canonicalToCurrentPackageNames(packageNames: Array<out String>): Array<String> {
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<PermissionInfo> {
return wrapped.queryPermissionsByGroup(permissionGroup, flags)
}
override fun getPermissionGroupInfo(permName: String, flags: Int): PermissionGroupInfo {
return wrapped.getPermissionGroupInfo(permName, flags)
}
override fun getAllPermissionGroups(flags: Int): MutableList<PermissionGroupInfo> {
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<ModuleInfo> {
return wrapped.getInstalledModules(flags)
}
override fun getInstalledPackages(flags: Int): MutableList<PackageInfo> {
return wrapped.getInstalledPackages(flags)
}
@TargetApi(18)
override fun getPackagesHoldingPermissions(permissions: Array<out String>, flags: Int): MutableList<PackageInfo> {
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<String>? {
return wrapped.getPackagesForUid(uid)
}
override fun getNameForUid(uid: Int): String? {
return wrapped.getNameForUid(uid)
}
override fun getInstalledApplications(flags: Int): MutableList<ApplicationInfo> {
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<String>? {
return wrapped.systemSharedLibraryNames
}
@TargetApi(26)
override fun getSharedLibraries(flags: Int): MutableList<SharedLibraryInfo> {
return wrapped.getSharedLibraries(flags)
}
@TargetApi(26)
override fun getChangedPackages(sequenceNumber: Int): ChangedPackages? {
return wrapped.getChangedPackages(sequenceNumber)
}
override fun getSystemAvailableFeatures(): Array<FeatureInfo> {
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<ResolveInfo> {
return wrapped.queryIntentActivities(intent, flags)
}
override fun queryIntentActivityOptions(caller: ComponentName?, specifics: Array<out Intent>?, intent: Intent, flags: Int): MutableList<ResolveInfo> {
return wrapped.queryIntentActivityOptions(caller, specifics, intent, flags)
}
override fun queryBroadcastReceivers(intent: Intent, flags: Int): MutableList<ResolveInfo> {
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<ResolveInfo> {
return wrapped.queryIntentServices(intent, flags)
}
@TargetApi(19)
override fun queryIntentContentProviders(intent: Intent, flags: Int): MutableList<ResolveInfo> {
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<ProviderInfo> {
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<InstrumentationInfo> {
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<PackageInfo> {
return wrapped.getPreferredPackages(flags)
}
override fun addPreferredActivity(filter: IntentFilter, match: Int, set: Array<out ComponentName>?, activity: ComponentName) {
return wrapped.addPreferredActivity(filter, match, set, activity)
}
override fun clearPackagePreferredActivities(packageName: String) {
return wrapped.clearPackagePreferredActivities(packageName)
}
override fun getPreferredActivities(outFilters: MutableList<IntentFilter>, outActivities: MutableList<ComponentName>, 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<String> {
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<String> {
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<String>) {
return wrapped.setMimeGroup(mimeGroup, mimeTypes)
}
}

View File

@ -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')

View File

@ -26,10 +26,12 @@
<permission
android:name="com.google.android.c2dm.permission.SEND"
android:label="@string/perm_c2dm_send_label"
android:protectionLevel="signature" />
android:permissionGroup="android.permission-group.NETWORK"
android:protectionLevel="privileged|signature" />
<permission
android:name="com.google.android.gtalkservice.permission.GTALK_SERVICE"
android:label="@string/perm_gtalk_svc_label"
android:permissionGroup="android.permission-group.MESSAGES"
android:protectionLevel="signature" />
<permission-tree
@ -109,14 +111,17 @@
tools:ignore="ProtectedPermissions" />
<application
android:name="androidx.multidex.MultiDexApplication"
android:allowBackup="true"
android:fullBackupOnly="true"
android:extractNativeLibs="false"
android:forceQueryable="true"
android:multiArch="true"
android:icon="@mipmap/ic_core_service_app"
android:label="@string/gms_app_name"
android:theme="@style/Theme.AppCompat.DayNight">
<library android:name="com.google.android.gms"/>
<meta-data
android:name="fake-signature"
android:value="@string/fake_signature" />
@ -294,16 +299,6 @@
<!-- DroidGuard / SafetyNet / reCAPTCHA -->
<service android:name="org.microg.gms.droidguard.DroidGuardService">
<intent-filter>
<action android:name="com.google.android.gms.droidguard.service.START" />
<action android:name="com.google.android.gms.droidguard.service.PING" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</service>
<receiver android:name="org.microg.gms.droidguard.ServiceInfoReceiver" />
<service android:name="org.microg.gms.safetynet.SafetyNetClientService">
<intent-filter>
<action android:name="com.google.android.gms.safetynet.service.START" />

View File

@ -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;

View File

@ -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)

View File

@ -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");
}
}

View File

@ -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<String, Any?> = 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"
}
}

View File

@ -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<String, String>): 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<String, String>): ByteArray =
getInstance(context).getResult(flow, data)
}
}
private class ConnectorDroidGuardResultCreator(private val context: Context) : DroidGuardResultCreator {
override suspend fun getResult(flow: String, data: Map<String, String>): 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<String, String>): 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<String, String>, listener: (String) -> Unit, errorListener: (VolleyError) -> Unit) : StringRequest(Method.POST, url, listener, errorListener) {
override fun getParams(): Map<String, String> = data
}

View File

@ -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)

View File

@ -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)

View File

@ -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;
}

View File

@ -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'

View File

@ -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;
}

View File

@ -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'

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ SPDX-FileCopyrightText: 2021, microG Project Team
~ SPDX-License-Identifier: Apache-2.0
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="org.microg.gms.droidguard.core">
<uses-permission android:name="android.permission.INTERNET" />
<application>
<service
android:name="org.microg.gms.droidguard.DroidGuardService"
android:enabled="true"
android:exported="true"
android:process="com.google.android.gms.unstable">
<intent-filter>
<action android:name="com.google.android.gms.droidguard.service.INIT" />
<action android:name="com.google.android.gms.droidguard.service.PING" />
<action android:name="com.google.android.gms.droidguard.service.START" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</service>
</application>
</manifest>

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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
* <p>
* 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
}
}

View File

@ -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();
}

View File

@ -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
}
}

View File

@ -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<String, ByteArray, ByteArray>? = 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()
}
}
}

View File

@ -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) {
}
}

View File

@ -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<Any?, Any?>): 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")
}
}

View File

@ -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 <T> 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
}
}

View File

@ -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<String, String>): 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<String, String>): 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<String, String>): 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<String, String>, listener: (String) -> Unit, errorListener: (VolleyError) -> Unit) : StringRequest(Method.POST, url, listener, errorListener) {
override fun getParams(): Map<String, String> = 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<String, String>): ByteArray {
val handle = client.getHandle().await()
try {
handle.init(flow)
return handle.guard(data)
} finally {
try {
handle.close()
} catch (e: Exception) {
// ignore
}
}
}
}

View File

@ -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<DroidGuardChimeraService>())

View File

@ -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)
}
}

View File

@ -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<Any?, Any?>?) {
Log.d(TAG, "guard()")
guardWithRequest(callbacks, flow, map, null)
}
override fun guardWithRequest(callbacks: IDroidGuardCallbacks?, flow: String?, map: MutableMap<Any?, Any?>?, 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"
}
}

View File

@ -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<Any?, Any?>, 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<Any?, Any?>, bytes: ByteArray?, flow: String?, context: Context, e: Throwable): ByteArray {
TODO("Not yet implemented")
}
}

View File

@ -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<Any, Any>): 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)
}
}
}

View File

@ -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<String, Class<*>>()
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<String, ByteArray, ByteArray>? {
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<String, ByteArray, ByteArray> {
return fetchFromServer(flow, createRequest(flow, packageName))
}
fun fetchFromServer(flow: String?, request: Request): Triple<String, ByteArray, ByteArray> {
val future = RequestFuture.newFuture<SignedResponse>()
queue.add(object : VolleyRequest<SignedResponse>(Method.POST, SERVER_URL, future) {
override fun parseNetworkResponse(response: NetworkResponse): VolleyResponse<SignedResponse> {
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<String, String> {
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<Certificate> = 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)
}
}

View File

@ -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
}
}
}

View File

@ -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")),
)
}
}

View File

@ -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 {

View File

@ -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<NoOptions>(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<DroidGuardHandle> {

View File

@ -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'