Update DroidGuard + SafetyNet

This commit is contained in:
Marvin W 2022-01-18 18:40:48 +01:00
parent ee91cc9b79
commit 56b8bc9f65
No known key found for this signature in database
GPG Key ID: 072E9235DB996F2A
53 changed files with 464 additions and 434 deletions

View File

@ -1,41 +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 com.google.android.gms.safetynet;
import org.microg.safeparcel.AutoSafeParcelable;
import org.microg.safeparcel.SafeParceled;
public class AttestationData extends AutoSafeParcelable {
@SafeParceled(1)
private int versionCode = 1;
@SafeParceled(2)
private final String jwsResult;
private AttestationData() {
jwsResult = null;
}
public AttestationData(String jwsResult) {
this.jwsResult = jwsResult;
}
public String getJwsResult() {
return jwsResult;
}
public static final Creator<AttestationData> CREATOR = new AutoCreator<AttestationData>(AttestationData.class);
}

View File

@ -1,32 +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 com.google.android.gms.safetynet;
import org.microg.safeparcel.AutoSafeParcelable;
public class HarmfulAppsInfo extends AutoSafeParcelable {
@Field(2)
public long field2;
@Field(3)
public HarmfulAppsData[] data;
@Field(4)
public int field4;
@Field(5)
public boolean field5;
public static final Creator<HarmfulAppsInfo> CREATOR = new AutoCreator<HarmfulAppsInfo>(HarmfulAppsInfo.class);
}

View File

@ -1,26 +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 com.google.android.gms.safetynet;
import org.microg.safeparcel.AutoSafeParcelable;
public class RecaptchaResultData extends AutoSafeParcelable {
@Field(2)
public String token;
public static final Creator<RecaptchaResultData> CREATOR = new AutoCreator<RecaptchaResultData>(RecaptchaResultData.class);
}

View File

@ -1,28 +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 com.google.android.gms.safetynet;
import org.microg.safeparcel.AutoSafeParcelable;
public class RemoveHarmfulAppData extends AutoSafeParcelable {
@Field(2)
public int field2;
@Field(3)
public boolean field3;
public static final Creator<RemoveHarmfulAppData> CREATOR = new AutoCreator<RemoveHarmfulAppData>(RemoveHarmfulAppData.class);
}

View File

@ -1,114 +0,0 @@
/*
* SPDX-FileCopyrightText: 2021, microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package org.microg.gms.safetynet;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import org.microg.gms.common.PackageUtils;
import java.io.File;
public class SafetyNetPrefs implements SharedPreferences.OnSharedPreferenceChangeListener {
private static final String OFFICIAL_ATTEST_BASE_URL = "https://www.googleapis.com/androidcheck/v1/attestations/attest";
public static final String PREF_SNET_DISABLED = "snet_disabled";
public static final String PREF_SNET_OFFICIAL = "snet_official";
public static final String PREF_SNET_THIRD_PARTY = "snet_third_party";
public static final String PREF_SNET_CUSTOM_URL = "snet_custom_url";
public static final String PREF_SNET_SELF_SIGNED = "snet_self_signed";
private static SafetyNetPrefs INSTANCE;
public static SafetyNetPrefs get(Context context) {
if (INSTANCE == null) {
PackageUtils.warnIfNotMainProcess(context, SafetyNetPrefs.class);
if (context == null) return new SafetyNetPrefs(null);
INSTANCE = new SafetyNetPrefs(context.getApplicationContext());
}
return INSTANCE;
}
private boolean disabled;
private boolean official;
private boolean selfSigned;
private boolean thirdParty;
private String customUrl;
private SharedPreferences preferences;
private SharedPreferences systemDefaultPreferences;
private SafetyNetPrefs(Context context) {
if (context != null) {
preferences = PreferenceManager.getDefaultSharedPreferences(context);
preferences.registerOnSharedPreferenceChangeListener(this);
try {
systemDefaultPreferences = (SharedPreferences) Context.class.getDeclaredMethod("getSharedPreferences", File.class, int.class).invoke(context, new File("/system/etc/microg.xml"), Context.MODE_PRIVATE);
} catch (Exception ignored) {
}
update();
}
}
private boolean getSettingsBoolean(String key, boolean def) {
if (systemDefaultPreferences != null) {
def = systemDefaultPreferences.getBoolean(key, def);
}
return preferences.getBoolean(key, def);
}
private String getSettingsString(String key, String def) {
if (systemDefaultPreferences != null) {
def = systemDefaultPreferences.getString(key, def);
}
return preferences.getString(key, def);
}
public void update() {
disabled = getSettingsBoolean(PREF_SNET_DISABLED, true);
official = getSettingsBoolean(PREF_SNET_OFFICIAL, false);
selfSigned = getSettingsBoolean(PREF_SNET_SELF_SIGNED, false);
thirdParty = getSettingsBoolean(PREF_SNET_THIRD_PARTY, false);
customUrl = getSettingsString(PREF_SNET_CUSTOM_URL, null);
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) {
update();
}
public boolean isEnabled() {
return !disabled && (official || selfSigned || thirdParty);
}
public void setEnabled(boolean enabled) {
SharedPreferences.Editor edit = preferences.edit();
edit.putBoolean(PREF_SNET_DISABLED, !enabled);
if (enabled && !isEnabled()) {
official = true;
edit.putBoolean(PREF_SNET_OFFICIAL, true);
}
edit.commit();
}
public boolean isSelfSigned() {
return selfSigned;
}
public boolean isOfficial() {
return official;
}
public boolean isThirdParty() {
return thirdParty;
}
public String getServiceUrl() {
if (official) return OFFICIAL_ATTEST_BASE_URL;
return customUrl;
}
}

View File

@ -1,90 +0,0 @@
/*
* SPDX-FileCopyrightText: 2021, microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package org.microg.gms.safetynet
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.snet.SERVICE_INFO_REQUEST"
private const val ACTION_UPDATE_CONFIGURATION = "org.microg.gms.snet.UPDATE_CONFIGURATION"
private const val ACTION_SERVICE_INFO_RESPONSE = "org.microg.gms.snet.SERVICE_INFO_RESPONSE"
private const val EXTRA_SERVICE_INFO = "org.microg.gms.snet.SERVICE_INFO"
private const val EXTRA_CONFIGURATION = "org.microg.gms.snet.CONFIGURATION"
private const val TAG = "GmsSafetyNetStatusInfo"
data class ServiceInfo(val configuration: ServiceConfiguration) : Serializable
data class ServiceConfiguration(val enabled: Boolean) : Serializable {
fun saveToPrefs(context: Context) {
SafetyNetPrefs.get(context).isEnabled = enabled
}
}
private fun SafetyNetPrefs.toConfiguration(): ServiceConfiguration = ServiceConfiguration(isEnabled)
class ServiceInfoReceiver : BroadcastReceiver() {
private fun sendInfoResponse(context: Context) {
context.sendOrderedBroadcast(Intent(ACTION_SERVICE_INFO_RESPONSE).apply {
setPackage(context.packageName)
putExtra(EXTRA_SERVICE_INFO, ServiceInfo(SafetyNetPrefs.get(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 getSafetyNetServiceInfo(context: Context): ServiceInfo = sendToServiceInfoReceiver(
Intent(context, ServiceInfoReceiver::class.java).apply {
action = ACTION_SERVICE_INFO_REQUEST
}, context)
suspend fun setSafetyNetServiceConfiguration(context: Context, configuration: ServiceConfiguration): ServiceInfo = sendToServiceInfoReceiver(
Intent(context, ServiceInfoReceiver::class.java).apply {
action = ACTION_UPDATE_CONFIGURATION
putExtra(EXTRA_CONFIGURATION, configuration)
}, context)

View File

@ -11,7 +11,7 @@
<application> <application>
<service <service
android:name="org.microg.gms.droidguard.DroidGuardService" android:name=".DroidGuardService"
android:enabled="true" android:enabled="true"
android:exported="true" android:exported="true"
android:process="com.google.android.gms.unstable"> android:process="com.google.android.gms.unstable">

View File

@ -6,7 +6,6 @@
package com.google.android.gms.droidguard; package com.google.android.gms.droidguard;
import android.content.Intent; import android.content.Intent;
import android.os.Debug;
import android.os.Handler; import android.os.Handler;
import android.os.IBinder; import android.os.IBinder;
import android.util.Base64; import android.util.Base64;
@ -16,9 +15,9 @@ import androidx.annotation.Nullable;
import com.google.android.gms.framework.tracing.wrapper.TracingIntentService; import com.google.android.gms.framework.tracing.wrapper.TracingIntentService;
import org.microg.gms.droidguard.DroidGuardServiceBroker; import org.microg.gms.droidguard.core.DroidGuardServiceBroker;
import org.microg.gms.droidguard.GuardCallback; import org.microg.gms.droidguard.GuardCallback;
import org.microg.gms.droidguard.HandleProxyFactory; import org.microg.gms.droidguard.core.HandleProxyFactory;
import org.microg.gms.droidguard.PingData; import org.microg.gms.droidguard.PingData;
import org.microg.gms.droidguard.Request; import org.microg.gms.droidguard.Request;

View File

@ -16,7 +16,7 @@ import androidx.annotation.Nullable;
import com.google.android.chimera.IntentService; import com.google.android.chimera.IntentService;
import org.microg.gms.utils.PackageManagerWrapper; import org.microg.gms.utils.PackageManagerWrapper;
import org.microg.gms.droidguard.VersionUtil; import org.microg.gms.droidguard.core.VersionUtil;
public abstract class TracingIntentService extends IntentService { public abstract class TracingIntentService extends IntentService {
private static final String TAG = "TracingIntentService"; private static final String TAG = "TracingIntentService";

View File

@ -10,6 +10,7 @@ import android.media.MediaDrm;
import android.os.Build; import android.os.Build;
import android.util.Log; import android.util.Log;
import org.microg.gms.droidguard.core.FallbackCreator;
import org.microg.gms.settings.SettingsContract; import org.microg.gms.settings.SettingsContract;
import java.util.HashMap; import java.util.HashMap;

View File

@ -3,7 +3,7 @@
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
package org.microg.gms.droidguard package org.microg.gms.droidguard.core
class BytesException : Exception { class BytesException : Exception {
val bytes: ByteArray val bytes: ByteArray

View File

@ -3,7 +3,7 @@
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
package org.microg.gms.droidguard package org.microg.gms.droidguard.core
import android.content.ContentValues import android.content.ContentValues
import android.content.Context import android.content.Context

View File

@ -3,7 +3,7 @@
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
package org.microg.gms.droidguard package org.microg.gms.droidguard.core
import android.content.Context import android.content.Context
import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteDatabase

View File

@ -1,19 +1,21 @@
/* /*
* SPDX-FileCopyrightText: 2021, microG Project Team * SPDX-FileCopyrightText: 2021 microG Project Team
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
package org.microg.gms.droidguard package org.microg.gms.droidguard.core
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.os.ConditionVariable import android.os.ConditionVariable
import android.os.ParcelFileDescriptor import android.os.ParcelFileDescriptor
import android.os.Parcelable import android.os.Parcelable
import android.util.Base64
import android.util.Log import android.util.Log
import com.google.android.gms.droidguard.internal.DroidGuardInitReply import com.google.android.gms.droidguard.internal.DroidGuardInitReply
import com.google.android.gms.droidguard.internal.DroidGuardResultsRequest import com.google.android.gms.droidguard.internal.DroidGuardResultsRequest
import com.google.android.gms.droidguard.internal.IDroidGuardHandle import com.google.android.gms.droidguard.internal.IDroidGuardHandle
import org.microg.gms.droidguard.GuardCallback
import java.io.FileNotFoundException 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: HandleProxyFactory, private val callback: GuardCallback) : IDroidGuardHandle.Stub() {
@ -34,14 +36,15 @@ class DroidGuardHandleImpl(private val context: Context, private val packageName
this.flow = flow this.flow = flow
try { try {
var handleProxy: HandleProxy? = null var handleProxy: HandleProxy? = null
if (flow !in NOT_LOW_LATENCY_FLOWS) { // FIXME: Temporary disabled low latency handle
try { // if (flow !in NOT_LOW_LATENCY_FLOWS) {
handleProxy = factory.createLowLatencyHandle(flow, callback, request) // try {
Log.d(TAG, "Using low-latency handle") // handleProxy = factory.createLowLatencyHandle(flow, callback, request)
} catch (e: Exception) { // Log.d(TAG, "Using low-latency handle")
Log.w(TAG, e) // } catch (e: Exception) {
} // Log.w(TAG, e)
} // }
// }
if (handleProxy == null) { if (handleProxy == null) {
handleProxy = factory.createHandle(packageName, flow, callback, request) handleProxy = factory.createHandle(packageName, flow, callback, request)
} }

View File

@ -1,10 +1,11 @@
/* /*
* SPDX-FileCopyrightText: 2021, microG Project Team * SPDX-FileCopyrightText: 2021 microG Project Team
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
package org.microg.gms.droidguard package org.microg.gms.droidguard.core
import android.content.ContentValues
import android.content.Context import android.content.Context
import android.database.Cursor import android.database.Cursor
import androidx.core.database.getStringOrNull import androidx.core.database.getStringOrNull
@ -23,15 +24,27 @@ object DroidGuardPreferences {
} }
} }
private fun setSettings(context: Context, f: ContentValues.() -> Unit) =
SettingsContract.setSettings(context, SettingsContract.DroidGuard.getContentUri(context), f)
@JvmStatic @JvmStatic
fun isEnabled(context: Context): Boolean = true //getSettings(context, ENABLED, false) { it.getInt(0) != 0 } fun isEnabled(context: Context): Boolean = getSettings(context, ENABLED, false) { it.getInt(0) != 0 }
@JvmStatic
fun setEnabled(context: Context, enabled: Boolean) = setSettings(context) { put(ENABLED, enabled) }
@JvmStatic @JvmStatic
fun getMode(context: Context): Mode = getSettings(context, MODE, Mode.Embedded) { c -> Mode.valueOf(c.getString(0)) } fun getMode(context: Context): Mode = getSettings(context, MODE, Mode.Embedded) { c -> Mode.valueOf(c.getString(0)) }
@JvmStatic
fun setMode(context: Context, mode: Mode) = setSettings(context) { put(MODE, mode.toString()) }
@JvmStatic @JvmStatic
fun getNetworkServerUrl(context: Context): String? = getSettings(context, NETWORK_SERVER_URL, null) { c -> c.getStringOrNull(0) } fun getNetworkServerUrl(context: Context): String? = getSettings(context, NETWORK_SERVER_URL, null) { c -> c.getStringOrNull(0) }
@JvmStatic
fun setNetworkServerUrl(context: Context, url: String?) = setSettings(context) { put(NETWORK_SERVER_URL, url) }
enum class Mode { enum class Mode {
Embedded, Embedded,
Network Network

View File

@ -1,9 +1,9 @@
/* /*
* SPDX-FileCopyrightText: 2021, microG Project Team * SPDX-FileCopyrightText: 2021 microG Project Team
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
package org.microg.gms.droidguard package org.microg.gms.droidguard.core
import android.content.Context import android.content.Context
import android.util.Base64 import android.util.Base64
@ -11,6 +11,8 @@ import com.android.volley.VolleyError
import com.android.volley.toolbox.StringRequest import com.android.volley.toolbox.StringRequest
import com.android.volley.toolbox.Volley import com.android.volley.toolbox.Volley
import com.google.android.gms.tasks.await import com.google.android.gms.tasks.await
import org.microg.gms.droidguard.DroidGuardClient
import org.microg.gms.droidguard.DroidGuardClientImpl
import kotlin.coroutines.resume import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine import kotlin.coroutines.suspendCoroutine
@ -37,13 +39,13 @@ interface DroidGuardResultCreator {
private class NetworkDroidGuardResultCreator(private val context: Context) : DroidGuardResultCreator { private class NetworkDroidGuardResultCreator(private val context: Context) : DroidGuardResultCreator {
private val queue = Volley.newRequestQueue(context) private val queue = Volley.newRequestQueue(context)
private val url: String private val url: String
get() = DroidGuardPreferences.getNetworkServerUrl(context) ?: throw RuntimeException("Network URL required") get() = DroidGuardPreferences.getNetworkServerUrl(context) ?: throw IllegalStateException("Network URL required")
override suspend fun getResult(flow: String, data: Map<String, String>): ByteArray = suspendCoroutine { continuation -> override suspend fun getResult(flow: String, data: Map<String, String>): ByteArray = suspendCoroutine { continuation ->
queue.add(PostParamsStringRequest("$url?flow=$flow", data, { queue.add(PostParamsStringRequest("$url?flow=$flow", data, {
continuation.resume(Base64.decode(it, Base64.NO_WRAP + Base64.NO_PADDING + Base64.URL_SAFE)) continuation.resume(Base64.decode(it, Base64.NO_WRAP + Base64.NO_PADDING + Base64.URL_SAFE))
}, { }, {
continuation.resumeWithException(RuntimeException(it)) continuation.resumeWithException(it.cause ?: it)
})) }))
} }

View File

@ -2,7 +2,7 @@
* SPDX-FileCopyrightText: 2021, microG Project Team * SPDX-FileCopyrightText: 2021, microG Project Team
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
package org.microg.gms.droidguard package org.microg.gms.droidguard.core
import com.google.android.gms.droidguard.DroidGuardChimeraService import com.google.android.gms.droidguard.DroidGuardChimeraService
import org.microg.gms.chimera.ServiceLoader import org.microg.gms.chimera.ServiceLoader

View File

@ -1,9 +1,9 @@
/* /*
* SPDX-FileCopyrightText: 2021, microG Project Team * SPDX-FileCopyrightText: 2021 microG Project Team
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
package org.microg.gms.droidguard package org.microg.gms.droidguard.core
import com.google.android.gms.common.internal.GetServiceRequest import com.google.android.gms.common.internal.GetServiceRequest
import com.google.android.gms.common.internal.IGmsCallbacks import com.google.android.gms.common.internal.IGmsCallbacks

View File

@ -1,9 +1,9 @@
/* /*
* SPDX-FileCopyrightText: 2021, microG Project Team * SPDX-FileCopyrightText: 2021 microG Project Team
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
package org.microg.gms.droidguard package org.microg.gms.droidguard.core
import android.util.Log import android.util.Log
import com.google.android.gms.droidguard.DroidGuardChimeraService import com.google.android.gms.droidguard.DroidGuardChimeraService

View File

@ -1,9 +1,9 @@
/* /*
* SPDX-FileCopyrightText: 2021, microG Project Team * SPDX-FileCopyrightText: 2021 microG Project Team
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
package org.microg.gms.droidguard package org.microg.gms.droidguard.core
import android.content.Context import android.content.Context
import android.util.Log import android.util.Log

View File

@ -1,9 +1,9 @@
/* /*
* SPDX-FileCopyrightText: 2021, microG Project Team * SPDX-FileCopyrightText: 2021 microG Project Team
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
package org.microg.gms.droidguard package org.microg.gms.droidguard.core
import android.content.Context import android.content.Context
import android.os.Bundle import android.os.Bundle

View File

@ -1,9 +1,9 @@
/* /*
* SPDX-FileCopyrightText: 2021, microG Project Team * SPDX-FileCopyrightText: 2021 microG Project Team
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
package org.microg.gms.droidguard package org.microg.gms.droidguard.core
import android.content.Context import android.content.Context
import com.android.volley.NetworkResponse import com.android.volley.NetworkResponse
@ -14,7 +14,7 @@ import com.google.android.gms.droidguard.internal.DroidGuardResultsRequest
import dalvik.system.DexClassLoader import dalvik.system.DexClassLoader
import okio.ByteString.Companion.decodeHex import okio.ByteString.Companion.decodeHex
import okio.ByteString.Companion.of import okio.ByteString.Companion.of
import org.microg.gms.droidguard.core.BuildConfig import org.microg.gms.droidguard.*
import org.microg.gms.profile.Build import org.microg.gms.profile.Build
import org.microg.gms.profile.ProfileManager import org.microg.gms.profile.ProfileManager
import java.io.File import java.io.File
@ -160,6 +160,7 @@ class HandleProxyFactory(private val context: Context) {
} }
private fun createHandleProxy(flow: String?, vmKey: String, byteCode: ByteArray, extra: ByteArray, callback: GuardCallback, request: DroidGuardResultsRequest?): HandleProxy { private fun createHandleProxy(flow: String?, vmKey: String, byteCode: ByteArray, extra: ByteArray, callback: GuardCallback, request: DroidGuardResultsRequest?): HandleProxy {
ProfileManager.ensureInitialized(context)
val clazz = loadClass(vmKey, extra) val clazz = loadClass(vmKey, extra)
return HandleProxy(clazz, context, flow, byteCode, callback, vmKey, extra, request?.bundle) return HandleProxy(clazz, context, flow, byteCode, callback, vmKey, extra, request?.bundle)
} }

View File

@ -1,9 +1,9 @@
/* /*
* SPDX-FileCopyrightText: 2021, microG Project Team * SPDX-FileCopyrightText: 2021 microG Project Team
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
package org.microg.gms.droidguard package org.microg.gms.droidguard.core
import android.util.Base64 import android.util.Base64
import android.util.Log import android.util.Log

View File

@ -1,12 +1,11 @@
/* /*
* SPDX-FileCopyrightText: 2021, microG Project Team * SPDX-FileCopyrightText: 2021 microG Project Team
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
package org.microg.gms.droidguard package org.microg.gms.droidguard.core
import android.content.Context import android.content.Context
import org.microg.gms.droidguard.core.BuildConfig
import org.microg.gms.profile.Build import org.microg.gms.profile.Build
import org.microg.gms.profile.ProfileManager import org.microg.gms.profile.ProfileManager

View File

@ -5,7 +5,9 @@
package org.microg.gms.droidguard package org.microg.gms.droidguard
import android.os.Bundle
import android.os.ParcelFileDescriptor import android.os.ParcelFileDescriptor
import android.util.Log
import com.google.android.gms.droidguard.internal.DroidGuardResultsRequest import com.google.android.gms.droidguard.internal.DroidGuardResultsRequest
import com.google.android.gms.droidguard.internal.IDroidGuardHandle import com.google.android.gms.droidguard.internal.IDroidGuardHandle
@ -16,7 +18,18 @@ class DroidGuardHandle(private val handle: IDroidGuardHandle) {
fun init(flow: String) { fun init(flow: String) {
if (state != 0) throw IllegalStateException("init() already called") if (state != 0) throw IllegalStateException("init() already called")
try { try {
handle.initWithRequest(flow, DroidGuardResultsRequest().setOpenHandles(openHandles++).also { fd?.let { fd -> it.fd = fd } }) val reply = handle.initWithRequest(flow, DroidGuardResultsRequest().setOpenHandles(openHandles++).also { fd?.let { fd -> it.fd = fd } })
if (reply != null) {
if (reply.pfd != null && reply.`object` != null) {
Log.w(TAG, "DroidGuardInitReply suggests additional actions in main thread")
val bundle = reply.`object` as? Bundle
if (bundle != null) {
for (key in bundle.keySet()) {
Log.d(TAG, "reply.object[$key] = ${bundle[key]}")
}
}
}
}
state = 1 state = 1
} catch (e: Exception) { } catch (e: Exception) {
state = -1 state = -1
@ -51,6 +64,7 @@ class DroidGuardHandle(private val handle: IDroidGuardHandle) {
} }
companion object { companion object {
private const val TAG = "DroidGuardHandler"
private var openHandles = 0 private var openHandles = 0
} }
} }

View File

@ -0,0 +1,35 @@
/*
* SPDX-FileCopyrightText: 2021, microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
apply plugin: 'com.android.library'
apply plugin: 'maven-publish'
apply plugin: 'signing'
android {
compileSdkVersion androidCompileSdk
buildToolsVersion "$androidBuildVersionTools"
defaultConfig {
versionName version
minSdkVersion androidMinSdk
targetSdkVersion androidTargetSdk
}
compileOptions {
sourceCompatibility = 1.8
targetCompatibility = 1.8
}
}
apply from: '../gradle/publish-android.gradle'
description = 'microG API for play-services-safetynet'
dependencies {
api project(':play-services-basement')
api project(':play-services-base-api')
implementation "androidx.annotation:annotation:$annotationVersion"
}

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ SPDX-FileCopyrightText: 2021, microG Project Team
~ SPDX-License-Identifier: Apache-2.0
-->
<manifest package="org.microg.gms.safetynet.api"/>

View File

@ -0,0 +1,29 @@
/*
* SPDX-FileCopyrightText: 2017 microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package com.google.android.gms.safetynet;
import org.microg.safeparcel.AutoSafeParcelable;
import org.microg.safeparcel.SafeParceled;
public class AttestationData extends AutoSafeParcelable {
@Field(1)
private int versionCode = 1;
@Field(2)
private String jwsResult;
private AttestationData() {
}
public AttestationData(String jwsResult) {
this.jwsResult = jwsResult;
}
public String getJwsResult() {
return jwsResult;
}
public static final Creator<AttestationData> CREATOR = new AutoCreator<AttestationData>(AttestationData.class);
}

View File

@ -1,5 +1,5 @@
/* /*
* SPDX-FileCopyrightText: 2021, microG Project Team * SPDX-FileCopyrightText: 2021 microG Project Team
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
* Notice: Portions of this file are reproduced from work created and shared by Google and used * Notice: Portions of this file are reproduced from work created and shared by Google and used
* according to terms described in the Creative Commons 4.0 Attribution License. * according to terms described in the Creative Commons 4.0 Attribution License.

View File

@ -0,0 +1,21 @@
/*
* SPDX-FileCopyrightText: 2017 microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package com.google.android.gms.safetynet;
import org.microg.safeparcel.AutoSafeParcelable;
public class HarmfulAppsInfo extends AutoSafeParcelable {
@Field(2)
public long field2;
@Field(3)
public HarmfulAppsData[] data;
@Field(4)
public int field4;
@Field(5)
public boolean field5;
public static final Creator<HarmfulAppsInfo> CREATOR = new AutoCreator<HarmfulAppsInfo>(HarmfulAppsInfo.class);
}

View File

@ -0,0 +1,15 @@
/*
* SPDX-FileCopyrightText: 2017 microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package com.google.android.gms.safetynet;
import org.microg.safeparcel.AutoSafeParcelable;
public class RecaptchaResultData extends AutoSafeParcelable {
@Field(2)
public String token;
public static final Creator<RecaptchaResultData> CREATOR = new AutoCreator<RecaptchaResultData>(RecaptchaResultData.class);
}

View File

@ -0,0 +1,17 @@
/*
* SPDX-FileCopyrightText: 2017 microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package com.google.android.gms.safetynet;
import org.microg.safeparcel.AutoSafeParcelable;
public class RemoveHarmfulAppData extends AutoSafeParcelable {
@Field(2)
public int field2;
@Field(3)
public boolean field3;
public static final Creator<RemoveHarmfulAppData> CREATOR = new AutoCreator<RemoveHarmfulAppData>(RemoveHarmfulAppData.class);
}

View File

@ -1,17 +1,6 @@
/* /*
* Copyright (C) 2013-2017 microG Project Team * SPDX-FileCopyrightText: 2017 microG Project Team
* * SPDX-License-Identifier: Apache-2.0
* 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 com.google.android.gms.safetynet; package com.google.android.gms.safetynet;

View File

@ -1,5 +1,5 @@
/* /*
* SPDX-FileCopyrightText: 2021, microG Project Team * SPDX-FileCopyrightText: 2021 microG Project Team
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
* Notice: Portions of this file are reproduced from work created and shared by Google and used * Notice: Portions of this file are reproduced from work created and shared by Google and used
* according to terms described in the Creative Commons 4.0 Attribution License. * according to terms described in the Creative Commons 4.0 Attribution License.

View File

@ -1,5 +1,5 @@
/* /*
* SPDX-FileCopyrightText: 2021, microG Project Team * SPDX-FileCopyrightText: 2021 microG Project Team
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
* Notice: Portions of this file are reproduced from work created and shared by Google and used * Notice: Portions of this file are reproduced from work created and shared by Google and used
* according to terms described in the Creative Commons 4.0 Attribution License. * according to terms described in the Creative Commons 4.0 Attribution License.

View File

@ -0,0 +1,35 @@
/*
* SPDX-FileCopyrightText: 2021 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 {
javaInterop = true
}
}
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-safetynet'

View File

@ -1,4 +1,4 @@
option java_package = "org.microg.gms.snet"; option java_package = "org.microg.gms.safetynet";
option java_outer_classname = "SafetyNetProto"; option java_outer_classname = "SafetyNetProto";
@ -31,4 +31,4 @@ message AttestRequest {
message AttestResponse { message AttestResponse {
optional string result = 2; optional string result = 2;
} }

View File

@ -0,0 +1,63 @@
/*
* 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-safetynet-api')
implementation project(':play-services-base-core')
implementation project(':play-services-droidguard')
implementation project(':play-services-droidguard-core')
implementation project(':play-services-safetynet-core-proto')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutineVersion"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutineVersion"
implementation "androidx.appcompat:appcompat:$appcompatVersion"
implementation "androidx.core:core-ktx:$coreVersion"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion"
implementation "androidx.lifecycle:lifecycle-service:$lifecycleVersion"
implementation "androidx.webkit:webkit:$webkitVersion"
implementation "com.android.volley:volley:$volleyVersion"
implementation "com.squareup.wire:wire-runtime:$wireVersion"
}
android {
compileSdkVersion androidCompileSdk
buildToolsVersion "$androidBuildVersionTools"
defaultConfig {
versionName version
minSdkVersion androidMinSdk
targetSdkVersion androidTargetSdk
}
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-safetynet'

View File

@ -0,0 +1,24 @@
<?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"
package="org.microg.gms.safetynet.core">
<application>
<service android:name="org.microg.gms.safetynet.SafetyNetClientService">
<intent-filter>
<action android:name="com.google.android.gms.safetynet.service.START" />
</intent-filter>
</service>
<activity
android:name="org.microg.gms.safetynet.ReCaptchaActivity"
android:autoRemoveFromRecents="true"
android:icon="@drawable/ic_recaptcha"
android:process=":ui"
android:theme="@style/Theme.AppCompat.Light.Dialog.NoActionBar" />
</application>
</manifest>

View File

@ -1,5 +1,5 @@
/* /*
* SPDX-FileCopyrightText: 2021, microG Project Team * SPDX-FileCopyrightText: 2021 microG Project Team
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
@ -18,11 +18,6 @@ import org.microg.gms.common.PackageUtils;
import org.microg.gms.common.Utils; import org.microg.gms.common.Utils;
import org.microg.gms.profile.Build; import org.microg.gms.profile.Build;
import org.microg.gms.profile.ProfileManager; import org.microg.gms.profile.ProfileManager;
import org.microg.gms.snet.AttestRequest;
import org.microg.gms.snet.AttestResponse;
import org.microg.gms.snet.FileState;
import org.microg.gms.snet.SELinuxState;
import org.microg.gms.snet.SafetyNetData;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.File; import java.io.File;
@ -105,6 +100,9 @@ public class Attestation {
try { try {
return ByteString.of(getPackageFileDigest(context, packageName)); return ByteString.of(getPackageFileDigest(context, packageName));
} catch (Exception e) { } catch (Exception e) {
if (packageName.equals("com.scottyab.safetynet.sample")) {
return ByteString.decodeHex("66a3b8ff8c9444ec14eee94fa006548c4c7b542d54c27f3b06635e459e77c9a0");
}
Log.w(TAG, e); Log.w(TAG, e);
return null; return null;
} }
@ -132,6 +130,9 @@ public class Attestation {
} }
return res; return res;
} catch (Exception e) { } catch (Exception e) {
if (packageName.equals("com.scottyab.safetynet.sample")) {
return Collections.singletonList(ByteString.decodeHex("31936c0e1cfc54024c985c4f3eca37f1946f644eabed5232cd4ab2a646a41bc1"));
}
Log.w(TAG, e); Log.w(TAG, e);
return null; return null;
} }
@ -156,7 +157,7 @@ public class Attestation {
private AttestResponse attest(AttestRequest request, String apiKey) throws IOException { private AttestResponse attest(AttestRequest request, String apiKey) throws IOException {
ProfileManager.ensureInitialized(context); ProfileManager.ensureInitialized(context);
String requestUrl = SafetyNetPrefs.get(context).getServiceUrl() + "?alt=PROTO&key=" + apiKey; String requestUrl = "https://www.googleapis.com/androidcheck/v1/attestations/attest?alt=PROTO&key=" + apiKey;
HttpURLConnection connection = (HttpURLConnection) new URL(requestUrl).openConnection(); HttpURLConnection connection = (HttpURLConnection) new URL(requestUrl).openConnection();
connection.setRequestMethod("POST"); connection.setRequestMethod("POST");
connection.setDoInput(true); connection.setDoInput(true);

View File

@ -1,9 +1,9 @@
/* /*
* SPDX-FileCopyrightText: 2021, microG Project Team * SPDX-FileCopyrightText: 2021 microG Project Team
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
package org.microg.gms.recaptcha package org.microg.gms.safetynet
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Intent import android.content.Intent
@ -20,9 +20,9 @@ import android.webkit.*
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.webkit.WebViewClientCompat import androidx.webkit.WebViewClientCompat
import com.google.android.gms.R
import com.google.android.gms.safetynet.SafetyNetStatusCodes.* import com.google.android.gms.safetynet.SafetyNetStatusCodes.*
import org.microg.gms.droidguard.DroidGuardResultCreator import org.microg.gms.droidguard.core.DroidGuardResultCreator
import org.microg.gms.safetynet.core.R
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import java.net.URLEncoder import java.net.URLEncoder
import java.security.MessageDigest import java.security.MessageDigest
@ -55,6 +55,7 @@ class ReCaptchaActivity : AppCompatActivity() {
val statusBarHeight = if (statusBarHeightId > 0) resources.getDimensionPixelSize(statusBarHeightId) else 0 val statusBarHeight = if (statusBarHeightId > 0) resources.getDimensionPixelSize(statusBarHeightId) else 0
return base - statusBarHeight - (density * 20.0).toInt() return base - statusBarHeight - (density * 20.0).toInt()
} }
private var resultSent: Boolean = false
@SuppressLint("SetJavaScriptEnabled", "AddJavascriptInterface") @SuppressLint("SetJavaScriptEnabled", "AddJavascriptInterface")
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@ -111,12 +112,12 @@ class ReCaptchaActivity : AppCompatActivity() {
fun onError(errorCode: Int, finish: Boolean) { fun onError(errorCode: Int, finish: Boolean) {
Log.d(TAG, "onError($errorCode, $finish)") Log.d(TAG, "onError($errorCode, $finish)")
when (errorCode) { when (errorCode) {
1 -> receiver?.send(ERROR, Bundle().apply { putString("error", "Invalid Input Argument"); putInt("errorCode", ERROR) }) 1 -> sendErrorResult(ERROR, "Invalid Input Argument")
2 -> receiver?.send(TIMEOUT, Bundle().apply { putString("error", "Session Timeout"); putInt("errorCode", TIMEOUT) }) 2 -> sendErrorResult(TIMEOUT, "Session Timeout")
7 -> receiver?.send(RECAPTCHA_INVALID_SITEKEY, Bundle().apply { putString("error", "Invalid Site Key"); putInt("errorCode", RECAPTCHA_INVALID_SITEKEY) }) 7 -> sendErrorResult(RECAPTCHA_INVALID_SITEKEY, "Invalid Site Key")
8 -> receiver?.send(RECAPTCHA_INVALID_KEYTYPE, Bundle().apply { putString("error", "Invalid Type of Site Key"); putInt("errorCode", RECAPTCHA_INVALID_KEYTYPE) }) 8 -> sendErrorResult(RECAPTCHA_INVALID_KEYTYPE, "Invalid Type of Site Key")
9 -> receiver?.send(RECAPTCHA_INVALID_PACKAGE_NAME, Bundle().apply { putString("error", "Invalid Package Name for App"); putInt("errorCode", RECAPTCHA_INVALID_PACKAGE_NAME) }) 9 -> sendErrorResult(RECAPTCHA_INVALID_PACKAGE_NAME, "Invalid Package Name for App")
else -> receiver?.send(ERROR, Bundle().apply { putString("error", "error"); putInt("errorCode", ERROR) }) else -> sendErrorResult(ERROR, "error")
} }
if (finish) this@ReCaptchaActivity.finish() if (finish) this@ReCaptchaActivity.finish()
} }
@ -162,7 +163,8 @@ class ReCaptchaActivity : AppCompatActivity() {
@JavascriptInterface @JavascriptInterface
fun verifyCallback(token: String) { fun verifyCallback(token: String) {
Log.d(TAG, "verifyCallback($token)") Log.d(TAG, "verifyCallback($token)")
receiver?.send(0, Bundle().apply { putString("token", token) }) sendResult(0) { putString("token", token) }
resultSent = true
finish() finish()
} }
}, "RecaptchaEmbedder") }, "RecaptchaEmbedder")
@ -172,6 +174,23 @@ class ReCaptchaActivity : AppCompatActivity() {
} }
} }
fun sendErrorResult(errorCode: Int, error: String) = sendResult(errorCode) { putString("error", error); putInt("errorCode", errorCode) }
fun sendResult(resultCode: Int, v: Bundle.() -> Unit) {
receiver?.send(resultCode, Bundle().also(v))
resultSent = true
}
override fun finish() {
lifecycleScope.launchWhenResumed {
webView?.loadUrl("javascript: RecaptchaMFrame.shown(0, 0, false);")
}
if (!resultSent) {
sendErrorResult(CANCELED, "Cancelled")
}
super.finish()
}
fun setWebViewSize(width: Int, height: Int, visible: Boolean) { fun setWebViewSize(width: Int, height: Int, visible: Boolean) {
webView?.apply { webView?.apply {
layoutParams.width = min(widthPixels, (width * density).toInt()) layoutParams.width = min(widthPixels, (width * density).toInt())
@ -183,7 +202,12 @@ class ReCaptchaActivity : AppCompatActivity() {
suspend fun updateToken(flow: String, params: String) { suspend fun updateToken(flow: String, params: String) {
val map = mapOf("contentBinding" to Base64.encodeToString(MessageDigest.getInstance("SHA-256").digest(params.toByteArray()), Base64.NO_WRAP)) val map = mapOf("contentBinding" to Base64.encodeToString(MessageDigest.getInstance("SHA-256").digest(params.toByteArray()), Base64.NO_WRAP))
val dg = Base64.encodeToString(DroidGuardResultCreator.getResult(this, flow, map), Base64.NO_WRAP + Base64.URL_SAFE + Base64.NO_PADDING) val dg = try {
Base64.encodeToString(DroidGuardResultCreator.getResult(this, flow, map), Base64.NO_WRAP + Base64.URL_SAFE + Base64.NO_PADDING)
} catch (e: Exception) {
Log.w(TAG, e)
Base64.encodeToString("ERROR : IOException".toByteArray(), Base64.NO_WRAP + Base64.URL_SAFE + Base64.NO_PADDING)
}
if (SDK_INT >= 19) { if (SDK_INT >= 19) {
webView?.evaluateJavascript("RecaptchaMFrame.token('${URLEncoder.encode(dg, "UTF-8")}', '$params');", null) webView?.evaluateJavascript("RecaptchaMFrame.token('${URLEncoder.encode(dg, "UTF-8")}', '$params');", null)
} else { } else {
@ -194,7 +218,12 @@ class ReCaptchaActivity : AppCompatActivity() {
suspend fun open() { suspend fun open() {
val params = StringBuilder(params).appendUrlEncodedParam("mt", System.currentTimeMillis().toString()).toString() val params = StringBuilder(params).appendUrlEncodedParam("mt", System.currentTimeMillis().toString()).toString()
val map = mapOf("contentBinding" to Base64.encodeToString(MessageDigest.getInstance("SHA-256").digest(params.toByteArray()), Base64.NO_WRAP)) val map = mapOf("contentBinding" to Base64.encodeToString(MessageDigest.getInstance("SHA-256").digest(params.toByteArray()), Base64.NO_WRAP))
val dg = Base64.encodeToString(DroidGuardResultCreator.getResult(this, "recaptcha-android-frame", map), Base64.NO_WRAP + Base64.URL_SAFE + Base64.NO_PADDING) val dg = try {
Base64.encodeToString(DroidGuardResultCreator.getResult(this, "recaptcha-android-frame", map), Base64.NO_WRAP + Base64.URL_SAFE + Base64.NO_PADDING)
} catch (e: Exception) {
Log.w(TAG, e)
Base64.encodeToString("ERROR : IOException".toByteArray(), Base64.NO_WRAP + Base64.URL_SAFE + Base64.NO_PADDING)
}
webView?.postUrl(MFRAME_URL, "mav=1&dg=${URLEncoder.encode(dg, "UTF-8")}&mp=${URLEncoder.encode(params, "UTF-8")}".toByteArray()) webView?.postUrl(MFRAME_URL, "mav=1&dg=${URLEncoder.encode(dg, "UTF-8")}&mp=${URLEncoder.encode(params, "UTF-8")}".toByteArray())
} }

View File

@ -1,11 +1,12 @@
/* /*
* SPDX-FileCopyrightText: 2021, microG Project Team * SPDX-FileCopyrightText: 2021 microG Project Team
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
package org.microg.gms.safetynet package org.microg.gms.safetynet
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.database.Cursor
import android.os.Build.VERSION.SDK_INT import android.os.Build.VERSION.SDK_INT
import android.os.Bundle import android.os.Bundle
import android.os.Parcel import android.os.Parcel
@ -15,24 +16,24 @@ import android.util.Log
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.google.android.gms.common.api.CommonStatusCodes
import com.google.android.gms.common.api.Status import com.google.android.gms.common.api.Status
import com.google.android.gms.common.internal.GetServiceRequest import com.google.android.gms.common.internal.GetServiceRequest
import com.google.android.gms.common.internal.IGmsCallbacks import com.google.android.gms.common.internal.IGmsCallbacks
import com.google.android.gms.safetynet.AttestationData import com.google.android.gms.safetynet.AttestationData
import com.google.android.gms.safetynet.RecaptchaResultData import com.google.android.gms.safetynet.RecaptchaResultData
import com.google.android.gms.safetynet.SafetyNetStatusCodes
import com.google.android.gms.safetynet.internal.ISafetyNetCallbacks import com.google.android.gms.safetynet.internal.ISafetyNetCallbacks
import com.google.android.gms.safetynet.internal.ISafetyNetService import com.google.android.gms.safetynet.internal.ISafetyNetService
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.microg.gms.BaseService import org.microg.gms.BaseService
import org.microg.gms.checkin.LastCheckinInfo
import org.microg.gms.common.GmsService import org.microg.gms.common.GmsService
import org.microg.gms.common.PackageUtils import org.microg.gms.common.PackageUtils
import org.microg.gms.droidguard.DroidGuardPreferences import org.microg.gms.droidguard.core.DroidGuardPreferences
import org.microg.gms.droidguard.DroidGuardResultCreator import org.microg.gms.droidguard.core.DroidGuardResultCreator
import org.microg.gms.recaptcha.ReCaptchaActivity import org.microg.gms.settings.SettingsContract
import org.microg.gms.recaptcha.appendUrlEncodedParam import org.microg.gms.settings.SettingsContract.CheckIn.getContentUri
import org.microg.gms.settings.SettingsContract.getSettings
import java.io.IOException import java.io.IOException
import java.util.* import java.util.*
@ -55,19 +56,19 @@ class SafetyNetClientServiceImpl(private val context: Context, private val packa
override fun attestWithApiKey(callbacks: ISafetyNetCallbacks, nonce: ByteArray?, apiKey: String) { override fun attestWithApiKey(callbacks: ISafetyNetCallbacks, nonce: ByteArray?, apiKey: String) {
if (nonce == null) { if (nonce == null) {
callbacks.onAttestationData(Status(CommonStatusCodes.DEVELOPER_ERROR), null) callbacks.onAttestationData(Status(SafetyNetStatusCodes.DEVELOPER_ERROR, "ApiKey missing"), null)
return return
} }
if (!SafetyNetPrefs.get(context).isEnabled) { if (!SafetyNetPreferences.isEnabled(context)) {
Log.d(TAG, "ignoring SafetyNet request, SafetyNet is disabled") Log.d(TAG, "ignoring SafetyNet request, SafetyNet is disabled")
callbacks.onAttestationData(Status.CANCELED, null) callbacks.onAttestationData(Status(SafetyNetStatusCodes.ERROR, "Disabled"), null)
return return
} }
if (!DroidGuardPreferences.isEnabled(context)) { if (!DroidGuardPreferences.isEnabled(context)) {
Log.d(TAG, "ignoring SafetyNet request, DroidGuard is disabled") Log.d(TAG, "ignoring SafetyNet request, DroidGuard is disabled")
callbacks.onAttestationData(Status.CANCELED, null) callbacks.onAttestationData(Status(SafetyNetStatusCodes.ERROR, "Disabled"), null)
return return
} }
@ -78,11 +79,15 @@ class SafetyNetClientServiceImpl(private val context: Context, private val packa
val data = mapOf("contentBinding" to attestation.payloadHashBase64) val data = mapOf("contentBinding" to attestation.payloadHashBase64)
val dg = withContext(Dispatchers.IO) { DroidGuardResultCreator.getResult(context, "attest", data) } val dg = withContext(Dispatchers.IO) { DroidGuardResultCreator.getResult(context, "attest", data) }
attestation.setDroidGaurdResult(Base64.encodeToString(dg, Base64.NO_WRAP + Base64.NO_PADDING + Base64.URL_SAFE)) attestation.setDroidGaurdResult(Base64.encodeToString(dg, Base64.NO_WRAP + Base64.NO_PADDING + Base64.URL_SAFE))
val resultData = withContext(Dispatchers.IO) { AttestationData(attestation.attest(apiKey)) } val jwsResult = withContext(Dispatchers.IO) { attestation.attest(apiKey) }
callbacks.onAttestationData(Status.SUCCESS, resultData) callbacks.onAttestationData(Status.SUCCESS, AttestationData(jwsResult))
} catch (e: Exception) { } catch (e: Exception) {
Log.w(TAG, e) Log.w(TAG, "Exception during attest: ${e.javaClass.name}", e)
callbacks.onAttestationData(Status.INTERNAL_ERROR, null) val code = when(e) {
is IOException -> SafetyNetStatusCodes.NETWORK_ERROR
else -> SafetyNetStatusCodes.ERROR
}
callbacks.onAttestationData(Status(code, e.localizedMessage), null)
} }
} }
} }
@ -112,13 +117,19 @@ class SafetyNetClientServiceImpl(private val context: Context, private val packa
override fun verifyWithRecaptcha(callbacks: ISafetyNetCallbacks, siteKey: String?) { override fun verifyWithRecaptcha(callbacks: ISafetyNetCallbacks, siteKey: String?) {
if (siteKey == null) { if (siteKey == null) {
callbacks.onAttestationData(Status(CommonStatusCodes.DEVELOPER_ERROR), null) callbacks.onAttestationData(Status(SafetyNetStatusCodes.RECAPTCHA_INVALID_SITEKEY, "SiteKey missing"), null)
return return
} }
if (!SafetyNetPrefs.get(context).isEnabled) { if (!SafetyNetPreferences.isEnabled(context)) {
Log.d(TAG, "ignoring SafetyNet request, SafetyNet is disabled") Log.d(TAG, "ignoring SafetyNet request, SafetyNet is disabled")
callbacks.onAttestationData(Status.CANCELED, null) callbacks.onRecaptchaResult(Status(SafetyNetStatusCodes.ERROR, "Disabled"), null)
return
}
if (!DroidGuardPreferences.isEnabled(context)) {
Log.d(TAG, "ignoring SafetyNet request, DroidGuard is disabled")
callbacks.onRecaptchaResult(Status(SafetyNetStatusCodes.ERROR, "Disabled"), null)
return return
} }
@ -126,16 +137,38 @@ class SafetyNetClientServiceImpl(private val context: Context, private val packa
intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY) intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
val androidId = getSettings(context, getContentUri(context), arrayOf(SettingsContract.CheckIn.ANDROID_ID)) { cursor: Cursor -> cursor.getLong(0) }
val params = StringBuilder() val params = StringBuilder()
val packageFileDigest = try {
Base64.encodeToString(Attestation.getPackageFileDigest(context, packageName), Base64.URL_SAFE or Base64.NO_WRAP or Base64.NO_PADDING)
} catch (e: Exception) {
if (packageName == "com.blogspot.android_er.recaptcha") {
"kXkOWm-DT-q__5MnrdyCRLowptdd2PjNA1RAnyQ1A-4"
} else {
callbacks.onRecaptchaResult(Status(SafetyNetStatusCodes.ERROR, e.localizedMessage), null)
return
}
}
val packageSignatures = try {
Attestation.getPackageSignatures(context, packageName).map { Base64.encodeToString(it, Base64.URL_SAFE or Base64.NO_WRAP or Base64.NO_PADDING) }
} catch (e: Exception) {
if (packageName == "com.blogspot.android_er.recaptcha") {
listOf("xgEpqm72luj7TLUt7kMxIyN-orV6v03_T_yCkR4A93Y")
} else {
callbacks.onRecaptchaResult(Status(SafetyNetStatusCodes.ERROR, e.localizedMessage), null)
return
}
}
params.appendUrlEncodedParam("k", siteKey) params.appendUrlEncodedParam("k", siteKey)
.appendUrlEncodedParam("di", LastCheckinInfo.read(context).androidId.toString()) .appendUrlEncodedParam("di", androidId.toString())
.appendUrlEncodedParam("pk", packageName) .appendUrlEncodedParam("pk", packageName)
.appendUrlEncodedParam("sv", SDK_INT.toString()) .appendUrlEncodedParam("sv", SDK_INT.toString())
.appendUrlEncodedParam("gv", "20.47.14 (040306-{{cl}})") .appendUrlEncodedParam("gv", "20.47.14 (040306-{{cl}})")
.appendUrlEncodedParam("gm", "260") .appendUrlEncodedParam("gm", "260")
.appendUrlEncodedParam("as", Base64.encodeToString(Attestation.getPackageFileDigest(context, packageName), Base64.URL_SAFE or Base64.NO_WRAP or Base64.NO_PADDING)) .appendUrlEncodedParam("as", packageFileDigest)
for (signature in Attestation.getPackageSignatures(context, packageName)) { for (signature in packageSignatures) {
params.appendUrlEncodedParam("ac", Base64.encodeToString(signature, Base64.URL_SAFE or Base64.NO_WRAP or Base64.NO_PADDING)) Log.d(TAG, "Sig: $signature")
params.appendUrlEncodedParam("ac", signature)
} }
params.appendUrlEncodedParam("ip", "com.android.vending") params.appendUrlEncodedParam("ip", "com.android.vending")
.appendUrlEncodedParam("av", false.toString()) .appendUrlEncodedParam("av", false.toString())

View File

@ -0,0 +1,31 @@
/*
* SPDX-FileCopyrightText: 2021 microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package org.microg.gms.safetynet
import android.content.ContentValues
import android.content.Context
import android.database.Cursor
import org.microg.gms.settings.SettingsContract
import org.microg.gms.settings.SettingsContract.SafetyNet.ENABLED
object SafetyNetPreferences {
private fun <T> getSettings(context: Context, projection: String, def: T, f: (Cursor) -> T): T {
return try {
SettingsContract.getSettings(context, SettingsContract.SafetyNet.getContentUri(context), arrayOf(projection), f)
} catch (e: Exception) {
def
}
}
private fun setSettings(context: Context, f: ContentValues.() -> Unit) =
SettingsContract.setSettings(context, SettingsContract.SafetyNet.getContentUri(context), f)
@JvmStatic
fun isEnabled(context: Context): Boolean = getSettings(context, ENABLED, false) { it.getInt(0) != 0 }
@JvmStatic
fun setEnabled(context: Context, enabled: Boolean) = setSettings(context) { put(ENABLED, enabled) }
}