mirror of
https://github.com/TeamVanced/VancedMicroG
synced 2024-12-24 03:35:50 +01:00
Add DroidGuard support
This commit is contained in:
parent
d8325870cb
commit
6d45bfb7ed
@ -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'
|
||||
|
@ -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";
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -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')
|
||||
|
@ -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" />
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
@ -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)
|
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
|
33
play-services-droidguard-core-proto/build.gradle
Normal file
33
play-services-droidguard-core-proto/build.gradle
Normal 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'
|
@ -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;
|
||||
}
|
59
play-services-droidguard-core/build.gradle
Normal file
59
play-services-droidguard-core/build.gradle
Normal 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'
|
28
play-services-droidguard-core/src/main/AndroidManifest.xml
Normal file
28
play-services-droidguard-core/src/main/AndroidManifest.xml
Normal 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>
|
@ -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();
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
@ -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) {
|
||||
}
|
||||
}
|
@ -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")
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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>())
|
@ -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)
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
@ -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")
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
@ -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")),
|
||||
)
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
@ -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> {
|
||||
|
@ -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'
|
||||
|
Loading…
Reference in New Issue
Block a user