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>
<service
android:name="org.microg.gms.droidguard.DroidGuardService"
android:name=".DroidGuardService"
android:enabled="true"
android:exported="true"
android:process="com.google.android.gms.unstable">

View File

@ -6,7 +6,6 @@
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;
@ -16,9 +15,9 @@ import androidx.annotation.Nullable;
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.HandleProxyFactory;
import org.microg.gms.droidguard.core.HandleProxyFactory;
import org.microg.gms.droidguard.PingData;
import org.microg.gms.droidguard.Request;

View File

@ -16,7 +16,7 @@ import androidx.annotation.Nullable;
import com.google.android.chimera.IntentService;
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 {
private static final String TAG = "TracingIntentService";

View File

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

View File

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

View File

@ -3,7 +3,7 @@
* 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

View File

@ -3,7 +3,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
package org.microg.gms.droidguard
package org.microg.gms.droidguard.core
import android.content.Context
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
*/
package org.microg.gms.droidguard
package org.microg.gms.droidguard.core
import android.annotation.SuppressLint
import android.content.Context
import android.os.ConditionVariable
import android.os.ParcelFileDescriptor
import android.os.Parcelable
import android.util.Base64
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.GuardCallback
import java.io.FileNotFoundException
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
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)
}
}
// FIXME: Temporary disabled low latency handle
// 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)
}

View File

@ -1,10 +1,11 @@
/*
* SPDX-FileCopyrightText: 2021, microG Project Team
* SPDX-FileCopyrightText: 2021 microG Project Team
* 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.database.Cursor
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
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
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
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 {
Embedded,
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
*/
package org.microg.gms.droidguard
package org.microg.gms.droidguard.core
import android.content.Context
import android.util.Base64
@ -11,6 +11,8 @@ 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 org.microg.gms.droidguard.DroidGuardClient
import org.microg.gms.droidguard.DroidGuardClientImpl
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine
@ -37,13 +39,13 @@ interface DroidGuardResultCreator {
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")
get() = DroidGuardPreferences.getNetworkServerUrl(context) ?: throw IllegalStateException("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))
continuation.resumeWithException(it.cause ?: it)
}))
}

View File

@ -2,7 +2,7 @@
* SPDX-FileCopyrightText: 2021, microG Project Team
* 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 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
*/
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.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
*/
package org.microg.gms.droidguard
package org.microg.gms.droidguard.core
import android.util.Log
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
*/
package org.microg.gms.droidguard
package org.microg.gms.droidguard.core
import android.content.Context
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
*/
package org.microg.gms.droidguard
package org.microg.gms.droidguard.core
import android.content.Context
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
*/
package org.microg.gms.droidguard
package org.microg.gms.droidguard.core
import android.content.Context
import com.android.volley.NetworkResponse
@ -14,7 +14,7 @@ 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.core.BuildConfig
import org.microg.gms.droidguard.*
import org.microg.gms.profile.Build
import org.microg.gms.profile.ProfileManager
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 {
ProfileManager.ensureInitialized(context)
val clazz = loadClass(vmKey, extra)
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
*/
package org.microg.gms.droidguard
package org.microg.gms.droidguard.core
import android.util.Base64
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
*/
package org.microg.gms.droidguard
package org.microg.gms.droidguard.core
import android.content.Context
import org.microg.gms.droidguard.core.BuildConfig
import org.microg.gms.profile.Build
import org.microg.gms.profile.ProfileManager

View File

@ -5,7 +5,9 @@
package org.microg.gms.droidguard
import android.os.Bundle
import android.os.ParcelFileDescriptor
import android.util.Log
import com.google.android.gms.droidguard.internal.DroidGuardResultsRequest
import com.google.android.gms.droidguard.internal.IDroidGuardHandle
@ -16,7 +18,18 @@ class DroidGuardHandle(private val handle: IDroidGuardHandle) {
fun init(flow: String) {
if (state != 0) throw IllegalStateException("init() already called")
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
} catch (e: Exception) {
state = -1
@ -51,6 +64,7 @@ class DroidGuardHandle(private val handle: IDroidGuardHandle) {
}
companion object {
private const val TAG = "DroidGuardHandler"
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
* 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.

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
*
* 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.
* SPDX-FileCopyrightText: 2017 microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
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
* 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.

View File

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2021, microG Project Team
* SPDX-FileCopyrightText: 2021 microG Project Team
* SPDX-License-Identifier: Apache-2.0
* 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.

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";
@ -31,4 +31,4 @@ message AttestRequest {
message AttestResponse {
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
*/
@ -18,11 +18,6 @@ import org.microg.gms.common.PackageUtils;
import org.microg.gms.common.Utils;
import org.microg.gms.profile.Build;
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.File;
@ -105,6 +100,9 @@ public class Attestation {
try {
return ByteString.of(getPackageFileDigest(context, packageName));
} catch (Exception e) {
if (packageName.equals("com.scottyab.safetynet.sample")) {
return ByteString.decodeHex("66a3b8ff8c9444ec14eee94fa006548c4c7b542d54c27f3b06635e459e77c9a0");
}
Log.w(TAG, e);
return null;
}
@ -132,6 +130,9 @@ public class Attestation {
}
return res;
} catch (Exception e) {
if (packageName.equals("com.scottyab.safetynet.sample")) {
return Collections.singletonList(ByteString.decodeHex("31936c0e1cfc54024c985c4f3eca37f1946f644eabed5232cd4ab2a646a41bc1"));
}
Log.w(TAG, e);
return null;
}
@ -156,7 +157,7 @@ public class Attestation {
private AttestResponse attest(AttestRequest request, String apiKey) throws IOException {
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();
connection.setRequestMethod("POST");
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
*/
package org.microg.gms.recaptcha
package org.microg.gms.safetynet
import android.annotation.SuppressLint
import android.content.Intent
@ -20,9 +20,9 @@ import android.webkit.*
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import androidx.webkit.WebViewClientCompat
import com.google.android.gms.R
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.net.URLEncoder
import java.security.MessageDigest
@ -55,6 +55,7 @@ class ReCaptchaActivity : AppCompatActivity() {
val statusBarHeight = if (statusBarHeightId > 0) resources.getDimensionPixelSize(statusBarHeightId) else 0
return base - statusBarHeight - (density * 20.0).toInt()
}
private var resultSent: Boolean = false
@SuppressLint("SetJavaScriptEnabled", "AddJavascriptInterface")
override fun onCreate(savedInstanceState: Bundle?) {
@ -111,12 +112,12 @@ class ReCaptchaActivity : AppCompatActivity() {
fun onError(errorCode: Int, finish: Boolean) {
Log.d(TAG, "onError($errorCode, $finish)")
when (errorCode) {
1 -> receiver?.send(ERROR, Bundle().apply { putString("error", "Invalid Input Argument"); putInt("errorCode", ERROR) })
2 -> receiver?.send(TIMEOUT, Bundle().apply { putString("error", "Session Timeout"); putInt("errorCode", TIMEOUT) })
7 -> receiver?.send(RECAPTCHA_INVALID_SITEKEY, Bundle().apply { putString("error", "Invalid Site Key"); putInt("errorCode", RECAPTCHA_INVALID_SITEKEY) })
8 -> receiver?.send(RECAPTCHA_INVALID_KEYTYPE, Bundle().apply { putString("error", "Invalid Type of Site Key"); putInt("errorCode", RECAPTCHA_INVALID_KEYTYPE) })
9 -> receiver?.send(RECAPTCHA_INVALID_PACKAGE_NAME, Bundle().apply { putString("error", "Invalid Package Name for App"); putInt("errorCode", RECAPTCHA_INVALID_PACKAGE_NAME) })
else -> receiver?.send(ERROR, Bundle().apply { putString("error", "error"); putInt("errorCode", ERROR) })
1 -> sendErrorResult(ERROR, "Invalid Input Argument")
2 -> sendErrorResult(TIMEOUT, "Session Timeout")
7 -> sendErrorResult(RECAPTCHA_INVALID_SITEKEY, "Invalid Site Key")
8 -> sendErrorResult(RECAPTCHA_INVALID_KEYTYPE, "Invalid Type of Site Key")
9 -> sendErrorResult(RECAPTCHA_INVALID_PACKAGE_NAME, "Invalid Package Name for App")
else -> sendErrorResult(ERROR, "error")
}
if (finish) this@ReCaptchaActivity.finish()
}
@ -162,7 +163,8 @@ class ReCaptchaActivity : AppCompatActivity() {
@JavascriptInterface
fun verifyCallback(token: String) {
Log.d(TAG, "verifyCallback($token)")
receiver?.send(0, Bundle().apply { putString("token", token) })
sendResult(0) { putString("token", token) }
resultSent = true
finish()
}
}, "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) {
webView?.apply {
layoutParams.width = min(widthPixels, (width * density).toInt())
@ -183,7 +202,12 @@ class ReCaptchaActivity : AppCompatActivity() {
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 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) {
webView?.evaluateJavascript("RecaptchaMFrame.token('${URLEncoder.encode(dg, "UTF-8")}', '$params');", null)
} else {
@ -194,7 +218,12 @@ class ReCaptchaActivity : AppCompatActivity() {
suspend fun open() {
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 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())
}

View File

@ -1,11 +1,12 @@
/*
* SPDX-FileCopyrightText: 2021, microG Project Team
* SPDX-FileCopyrightText: 2021 microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package org.microg.gms.safetynet
import android.content.Context
import android.content.Intent
import android.database.Cursor
import android.os.Build.VERSION.SDK_INT
import android.os.Bundle
import android.os.Parcel
@ -15,24 +16,24 @@ import android.util.Log
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
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.internal.GetServiceRequest
import com.google.android.gms.common.internal.IGmsCallbacks
import com.google.android.gms.safetynet.AttestationData
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.ISafetyNetService
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
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
import org.microg.gms.droidguard.core.DroidGuardPreferences
import org.microg.gms.droidguard.core.DroidGuardResultCreator
import org.microg.gms.settings.SettingsContract
import org.microg.gms.settings.SettingsContract.CheckIn.getContentUri
import org.microg.gms.settings.SettingsContract.getSettings
import java.io.IOException
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) {
if (nonce == null) {
callbacks.onAttestationData(Status(CommonStatusCodes.DEVELOPER_ERROR), null)
callbacks.onAttestationData(Status(SafetyNetStatusCodes.DEVELOPER_ERROR, "ApiKey missing"), null)
return
}
if (!SafetyNetPrefs.get(context).isEnabled) {
if (!SafetyNetPreferences.isEnabled(context)) {
Log.d(TAG, "ignoring SafetyNet request, SafetyNet is disabled")
callbacks.onAttestationData(Status.CANCELED, null)
callbacks.onAttestationData(Status(SafetyNetStatusCodes.ERROR, "Disabled"), null)
return
}
if (!DroidGuardPreferences.isEnabled(context)) {
Log.d(TAG, "ignoring SafetyNet request, DroidGuard is disabled")
callbacks.onAttestationData(Status.CANCELED, null)
callbacks.onAttestationData(Status(SafetyNetStatusCodes.ERROR, "Disabled"), null)
return
}
@ -78,11 +79,15 @@ class SafetyNetClientServiceImpl(private val context: Context, private val packa
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)
val jwsResult = withContext(Dispatchers.IO) { attestation.attest(apiKey) }
callbacks.onAttestationData(Status.SUCCESS, AttestationData(jwsResult))
} catch (e: Exception) {
Log.w(TAG, e)
callbacks.onAttestationData(Status.INTERNAL_ERROR, null)
Log.w(TAG, "Exception during attest: ${e.javaClass.name}", e)
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?) {
if (siteKey == null) {
callbacks.onAttestationData(Status(CommonStatusCodes.DEVELOPER_ERROR), null)
callbacks.onAttestationData(Status(SafetyNetStatusCodes.RECAPTCHA_INVALID_SITEKEY, "SiteKey missing"), null)
return
}
if (!SafetyNetPrefs.get(context).isEnabled) {
if (!SafetyNetPreferences.isEnabled(context)) {
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
}
@ -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_NEW_TASK)
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 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)
.appendUrlEncodedParam("di", LastCheckinInfo.read(context).androidId.toString())
.appendUrlEncodedParam("di", androidId.toString())
.appendUrlEncodedParam("pk", packageName)
.appendUrlEncodedParam("sv", SDK_INT.toString())
.appendUrlEncodedParam("gv", "20.47.14 (040306-{{cl}})")
.appendUrlEncodedParam("gm", "260")
.appendUrlEncodedParam("as", Base64.encodeToString(Attestation.getPackageFileDigest(context, packageName), Base64.URL_SAFE or Base64.NO_WRAP or Base64.NO_PADDING))
for (signature in Attestation.getPackageSignatures(context, packageName)) {
params.appendUrlEncodedParam("ac", Base64.encodeToString(signature, Base64.URL_SAFE or Base64.NO_WRAP or Base64.NO_PADDING))
.appendUrlEncodedParam("as", packageFileDigest)
for (signature in packageSignatures) {
Log.d(TAG, "Sig: $signature")
params.appendUrlEncodedParam("ac", signature)
}
params.appendUrlEncodedParam("ip", "com.android.vending")
.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) }
}