Skip to content

Commit

Permalink
Added initial support for Play Integrity service (#2599)
Browse files Browse the repository at this point in the history
Co-authored-by: Marvin W <[email protected]>
  • Loading branch information
DaVinci9196 and mar-v-in authored Dec 17, 2024
1 parent a33defb commit c65410a
Show file tree
Hide file tree
Showing 33 changed files with 1,847 additions and 123 deletions.
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ buildscript {
ext.slf4jVersion = '1.7.36'
ext.volleyVersion = '1.2.1'
ext.wireVersion = '4.8.0'
ext.tinkVersion = '1.13.0'

ext.androidBuildGradleVersion = '8.2.2'

Expand Down
5 changes: 5 additions & 0 deletions play-services-droidguard/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

apply plugin: 'com.android.library'
apply plugin: 'maven-publish'
apply plugin: 'kotlin-android'
apply plugin: 'signing'

android {
Expand All @@ -31,6 +32,10 @@ android {
sourceCompatibility = 1.8
targetCompatibility = 1.8
}

kotlinOptions {
jvmTarget = 1.8
}
}

apply from: '../gradle/publish-android.gradle'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

import org.microg.gms.droidguard.core.DroidGuardServiceBroker;
import org.microg.gms.droidguard.GuardCallback;
import org.microg.gms.droidguard.core.HandleProxyFactory;
import org.microg.gms.droidguard.core.NetworkHandleProxyFactory;
import org.microg.gms.droidguard.PingData;
import org.microg.gms.droidguard.Request;

Expand All @@ -30,7 +30,7 @@
public class DroidGuardChimeraService extends TracingIntentService {
public static final Object a = new Object();
// factory
public HandleProxyFactory b;
public NetworkHandleProxyFactory b;
// widevine
public Object c;
// executor
Expand All @@ -51,7 +51,7 @@ public DroidGuardChimeraService() {
setIntentRedelivery(true);
}

public DroidGuardChimeraService(HandleProxyFactory factory, Object ping, Object database) {
public DroidGuardChimeraService(NetworkHandleProxyFactory factory, Object ping, Object database) {
super("DG");
setIntentRedelivery(true);
this.b = factory;
Expand Down Expand Up @@ -120,7 +120,7 @@ public final IBinder onBind(Intent intent) {
@Override
public void onCreate() {
this.e = new Object();
this.b = new HandleProxyFactory(this);
this.b = new NetworkHandleProxyFactory(this);
this.g = new Object();
this.h = new Handler();
this.c = new Object();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@ 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 org.microg.gms.droidguard.BytesException
import org.microg.gms.droidguard.GuardCallback
import org.microg.gms.droidguard.HandleProxy
import java.io.FileNotFoundException

class DroidGuardHandleImpl(private val context: Context, private val packageName: String, private val factory: HandleProxyFactory, private val callback: GuardCallback) : IDroidGuardHandle.Stub() {
class DroidGuardHandleImpl(private val context: Context, private val packageName: String, private val factory: NetworkHandleProxyFactory, private val callback: GuardCallback) : IDroidGuardHandle.Stub() {
private val condition = ConditionVariable()

private var flow: String? = null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ object DroidGuardPreferences {
fun isAvailable(context: Context): Boolean = isEnabled(context) && (!isForcedLocalDisabled(context) || getMode(context) != Mode.Embedded)

@JvmStatic
fun isLocalAvailable(context: Context): Boolean = isEnabled(context) && !isForcedLocalDisabled(context)
fun isLocalAvailable(context: Context): Boolean = isEnabled(context) && !isForcedLocalDisabled(context) && getMode(context) == Mode.Embedded

@JvmStatic
fun setEnabled(context: Context, enabled: Boolean) = setSettings(context) { put(ENABLED, enabled) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ private class NetworkDroidGuardResultCreator(private val context: Context) : Dro
get() = DroidGuardPreferences.getNetworkServerUrl(context) ?: throw IllegalStateException("Network URL required")

override suspend fun getResults(flow: String, data: Map<String, String>): String = suspendCoroutine { continuation ->
queue.add(PostParamsStringRequest("$url?flow=$flow", data, {
queue.add(PostParamsStringRequest("$url?flow=$flow&source=${context.packageName}", data, {
continuation.resume(it)
}, {
continuation.resumeWithException(it.cause ?: it)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,23 @@
package org.microg.gms.droidguard.core

import android.content.Context
import androidx.annotation.GuardedBy
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.droidguard.*
import org.microg.gms.profile.Build
import org.microg.gms.profile.ProfileManager
import org.microg.gms.utils.singleInstanceOf
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) {
@GuardedBy("CLASS_LOCK")
private val classMap = hashMapOf<String, Class<*>>()
class NetworkHandleProxyFactory(private val context: Context) : HandleProxyFactory(context) {
private val dgDb: DgDatabaseHelper = DgDatabaseHelper(context)
private val version = VersionUtil(context)
private val queue = singleInstanceOf { Volley.newRequestQueue(context.applicationContext) }
Expand Down Expand Up @@ -136,7 +129,7 @@ class HandleProxyFactory(private val context: Context) {

override fun getHeaders(): Map<String, String> {
return mapOf(
"User-Agent" to "DroidGuard/${version.versionCode}"
"User-Agent" to "DroidGuard/${version.versionCode}"
)
}
})
Expand Down Expand Up @@ -178,68 +171,7 @@ class HandleProxyFactory(private val context: Context) {
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<*> {
synchronized(CLASS_LOCK) {
val cachedClass = classMap[vmKey]
if (cachedClass != null) {
updateCacheTimestamp(vmKey)
return cachedClass
}
val weakClass = weakClassMap[vmKey]
if (weakClass != null) {
classMap[vmKey] = weakClass
updateCacheTimestamp(vmKey)
return weakClass
}
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
weakClassMap[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 CLASS_LOCK = Object()
@GuardedBy("CLASS_LOCK")
val weakClassMap = WeakHashMap<String, Class<*>>()
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)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.ParcelFileDescriptor;
import android.util.Log;

import com.google.android.gms.droidguard.DroidGuardHandle;
Expand All @@ -29,6 +30,7 @@ public class DroidGuardApiClient extends GmsClient<IDroidGuardService> {
private final Context context;
private int openHandles = 0;
private Handler handler;
private HandleProxyFactory factory;

public DroidGuardApiClient(Context context, ConnectionCallbacks callbacks, OnConnectionFailedListener connectionFailedListener) {
super(context, callbacks, connectionFailedListener, GmsService.DROIDGUARD.ACTION);
Expand All @@ -38,6 +40,8 @@ public DroidGuardApiClient(Context context, ConnectionCallbacks callbacks, OnCon
HandlerThread thread = new HandlerThread("DG");
thread.start();
handler = new Handler(thread.getLooper());

factory = new HandleProxyFactory(context);
}

public void setPackageName(String packageName) {
Expand All @@ -60,6 +64,7 @@ public DroidGuardHandle openHandle(String flow, DroidGuardResultsRequest request
for (String key : bundle.keySet()) {
Log.d(TAG, "reply.object[" + key + "] = " + bundle.get(key));
}
handleDroidGuardData(reply.pfd, (Bundle) reply.object);
}
}
}
Expand All @@ -70,6 +75,16 @@ public DroidGuardHandle openHandle(String flow, DroidGuardResultsRequest request
}
}

private void handleDroidGuardData(ParcelFileDescriptor pfd, Bundle bundle) {
String vmKey = bundle.getString("h");
if (vmKey == null) {
throw new RuntimeException("Missing vmKey");
}
HandleProxy proxy = factory.createHandle(vmKey, pfd, bundle);
proxy.init();
proxy.close();
}

public void markHandleClosed() {
if (openHandles == 0) {
Log.w(TAG, "Can't mark handle closed if none is open");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* SPDX-License-Identifier: Apache-2.0
*/

package org.microg.gms.droidguard.core
package org.microg.gms.droidguard

class BytesException : Exception {
val bytes: ByteArray
Expand Down
Original file line number Diff line number Diff line change
@@ -1,30 +1,30 @@
/*
* SPDX-FileCopyrightText: 2021 microG Project Team
* SPDX-FileCopyrightText: 2022 microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/

package org.microg.gms.droidguard.core
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
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)
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 {
Expand Down
Loading

0 comments on commit c65410a

Please sign in to comment.