Loading play-services-base/src/main/java/org/microg/gms/common/api/GoogleApiManager.java +11 −3 Original line number Diff line number Diff line Loading @@ -51,7 +51,11 @@ public class GoogleApiManager { boolean connecting = client.isConnecting(); boolean connected = client.isConnected(); if (connected) { try { apiCall.execute(client, completionSource); } catch (Exception e) { completionSource.setException(e); } } else { waitingApiCallMap.get(new ApiInstance(api)).add(new WaitingApiCall<R>((PendingGoogleApiCall<R, ApiClient>) apiCall, completionSource)); if (!connecting) { Loading @@ -63,7 +67,11 @@ public class GoogleApiManager { private synchronized void onInstanceConnected(ApiInstance apiInstance, Bundle connectionHint) { List<WaitingApiCall<?>> waitingApiCalls = waitingApiCallMap.get(apiInstance); for (WaitingApiCall<?> waitingApiCall : waitingApiCalls) { try { waitingApiCall.execute(clientMap.get(apiInstance)); } catch (Exception e) { waitingApiCall.failed(e); } } waitingApiCalls.clear(); } Loading Loading @@ -120,7 +128,7 @@ public class GoogleApiManager { this.completionSource = completionSource; } public void execute(ApiClient client) { public void execute(ApiClient client) throws Exception { apiCall.execute(client, completionSource); } Loading play-services-base/src/main/java/org/microg/gms/common/api/PendingGoogleApiCall.java +1 −1 Original line number Diff line number Diff line Loading @@ -8,5 +8,5 @@ package org.microg.gms.common.api; import com.google.android.gms.tasks.TaskCompletionSource; public interface PendingGoogleApiCall<R, A extends ApiClient> { void execute(A client, TaskCompletionSource<R> completionSource); void execute(A client, TaskCompletionSource<R> completionSource) throws Exception; } play-services-core/build.gradle +2 −0 Original line number Diff line number Diff line Loading @@ -31,6 +31,7 @@ dependencies { implementation project(':play-services-location-core') withNearbyImplementation project(':play-services-nearby-core') implementation project(':play-services-oss-licenses-core') implementation project(':play-services-recaptcha-core') implementation project(':play-services-safetynet-core') implementation project(':play-services-tapandpay-core') implementation project(':play-services-vision-core') Loading @@ -49,6 +50,7 @@ dependencies { implementation project(':play-services-maps') implementation project(':play-services-measurement-base') implementation project(':play-services-places') implementation project(':play-services-recaptcha') implementation project(':play-services-safetynet') implementation project(':play-services-tasks-ktx') Loading play-services-core/src/main/AndroidManifest.xml +0 −1 Original line number Diff line number Diff line Loading @@ -804,7 +804,6 @@ <action android:name="com.google.android.gms.plus.service.START" /> <action android:name="com.google.android.gms.pseudonymous.service.START" /> <action android:name="com.google.android.gms.rcs.START" /> <action android:name="com.google.android.gms.recaptcha.service.START" /> <action android:name="com.google.android.gms.romanesco.MODULE_BACKUP_AGENT" /> <action android:name="com.google.android.gms.romanesco.service.START" /> <action android:name="com.google.android.gms.search.service.SEARCH_AUTH_START" /> Loading play-services-core/src/main/kotlin/org/microg/gms/ui/SafetyNetPreferencesFragment.kt +104 −23 Original line number Diff line number Diff line Loading @@ -9,27 +9,37 @@ import android.annotation.SuppressLint import android.os.Bundle import android.util.Base64 import android.util.Log import androidx.core.content.ContextCompat import androidx.core.os.bundleOf import androidx.navigation.fragment.findNavController import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController import androidx.preference.Preference import androidx.preference.PreferenceCategory import androidx.preference.PreferenceFragmentCompat import com.android.volley.toolbox.JsonObjectRequest import com.android.volley.toolbox.Volley import com.google.android.gms.R import com.google.android.gms.recaptcha.Recaptcha import com.google.android.gms.recaptcha.RecaptchaAction import com.google.android.gms.recaptcha.RecaptchaActionType import com.google.android.gms.safetynet.SafetyNet import com.google.android.gms.tasks.await import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.json.JSONObject import org.microg.gms.safetynet.SafetyNetDatabase import org.microg.gms.safetynet.SafetyNetRequestType import org.microg.gms.safetynet.SafetyNetRequestType.ATTESTATION import org.microg.gms.safetynet.SafetyNetRequestType.RECAPTCHA import java.net.URLEncoder import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException import kotlin.coroutines.suspendCoroutine import kotlin.random.Random class SafetyNetPreferencesFragment : PreferenceFragmentCompat() { private lateinit var runAttest: Preference private lateinit var runReCaptcha: Preference private lateinit var runReCaptchaEnterprise: Preference private lateinit var apps: PreferenceCategory private lateinit var appsAll: Preference private lateinit var appsNone: Preference Loading @@ -42,17 +52,24 @@ class SafetyNetPreferencesFragment : PreferenceFragmentCompat() { override fun onBindPreferences() { runAttest = preferenceScreen.findPreference("pref_safetynet_run_attest") ?: runAttest runReCaptcha = preferenceScreen.findPreference("pref_recaptcha_run_test") ?: runReCaptcha runReCaptchaEnterprise = preferenceScreen.findPreference("pref_recaptcha_enterprise_run_test") ?: runReCaptchaEnterprise apps = preferenceScreen.findPreference("prefcat_safetynet_apps") ?: apps appsAll = preferenceScreen.findPreference("pref_safetynet_apps_all") ?: appsAll appsNone = preferenceScreen.findPreference("pref_safetynet_apps_none") ?: appsNone runAttest.isVisible = SAFETYNET_API_KEY != null runReCaptcha.isVisible = RECAPTCHA_SITE_KEY != null runReCaptchaEnterprise.isVisible = RECAPTCHA_ENTERPRISE_SITE_KEY != null runAttest.setOnPreferenceClickListener { val context = context ?: return@setOnPreferenceClickListener false runAttest.setIcon(R.drawable.ic_circle_pending) runAttest.setSummary(R.string.pref_test_summary_running) lifecycleScope.launchWhenResumed { try { val response = SafetyNet.getClient(requireActivity()) .attest(Random.nextBytes(32), "AIzaSyCcJO6IZiA5Or_AXw3LFdaTCmpnfL4pJ-Q").await() .attest(Random.nextBytes(32), SAFETYNET_API_KEY).await() val (_, payload, _) = try { response.jwsResult.split(".") } catch (e: Exception) { Loading @@ -68,6 +85,10 @@ class SafetyNetPreferencesFragment : PreferenceFragmentCompat() { runAttest.summary = summary runAttest.icon = icon } } catch (e: Exception) { runAttest.summary = getString(R.string.pref_test_summary_failed, e.message) runAttest.icon = ContextCompat.getDrawable(context, R.drawable.ic_circle_warn) } updateContent() } true Loading @@ -77,13 +98,66 @@ class SafetyNetPreferencesFragment : PreferenceFragmentCompat() { runReCaptcha.setIcon(R.drawable.ic_circle_pending) runReCaptcha.setSummary(R.string.pref_test_summary_running) lifecycleScope.launchWhenResumed { try { val response = SafetyNet.getClient(requireActivity()) .verifyWithRecaptcha("6Lc4TzgeAAAAAJnW7Jbo6UtQ0xGuTKjHAeyhINuq").await() formatSummaryForSafetyNetResult(context, response.tokenResult, response.result.status, RECAPTCHA) .verifyWithRecaptcha(RECAPTCHA_SITE_KEY).await() val result = if (response.tokenResult != null) { val queue = Volley.newRequestQueue(context) val json = if (RECAPTCHA_SECRET != null) { suspendCoroutine { continuation -> queue.add(object : JsonObjectRequest( Method.POST, "https://www.google.com/recaptcha/api/siteverify", null, { continuation.resume(it) }, { continuation.resumeWithException(it) } ) { override fun getBodyContentType(): String = "application/x-www-form-urlencoded; charset=UTF-8" override fun getBody(): ByteArray = "secret=$RECAPTCHA_SECRET&response=${URLEncoder.encode(response.tokenResult, "UTF-8")}".encodeToByteArray() }) } } else { // Can't properly verify, everything becomes a success JSONObject(mapOf("success" to true)) } Log.d(TAG, "Result: $json") json.toString() } else { null } formatSummaryForSafetyNetResult(context, result, response.result.status, RECAPTCHA) .let { (summary, icon) -> runReCaptcha.summary = summary runReCaptcha.icon = icon } } catch (e: Exception) { runReCaptcha.summary = getString(R.string.pref_test_summary_failed, e.message) runReCaptcha.icon = ContextCompat.getDrawable(context, R.drawable.ic_circle_warn) } updateContent() } true } runReCaptchaEnterprise.setOnPreferenceClickListener { val context = context ?: return@setOnPreferenceClickListener false runReCaptchaEnterprise.setIcon(R.drawable.ic_circle_pending) runReCaptchaEnterprise.setSummary(R.string.pref_test_summary_running) lifecycleScope.launchWhenResumed { try { val client = Recaptcha.getClient(requireActivity()) val handle = client.init(RECAPTCHA_ENTERPRISE_SITE_KEY).await() val result = client.execute(handle, RecaptchaAction(RecaptchaActionType(RecaptchaActionType.SIGNUP))).await() Log.d(TAG, "Recaptcha Token: " + result.tokenResult) client.close(handle).await() runReCaptchaEnterprise.summary = getString(R.string.pref_test_summary_warn, "Incomplete Test") runReCaptchaEnterprise.icon = ContextCompat.getDrawable(context, R.drawable.ic_circle_warn) } catch (e: Exception) { runReCaptchaEnterprise.summary = getString(R.string.pref_test_summary_failed, e.message) runReCaptchaEnterprise.icon = ContextCompat.getDrawable(context, R.drawable.ic_circle_warn) } updateContent() } true Loading Loading @@ -143,4 +217,11 @@ class SafetyNetPreferencesFragment : PreferenceFragmentCompat() { } } companion object { private val SAFETYNET_API_KEY: String? = "AIzaSyCcJO6IZiA5Or_AXw3LFdaTCmpnfL4pJ-Q" private val RECAPTCHA_SITE_KEY: String? = "6Lc4TzgeAAAAAJnW7Jbo6UtQ0xGuTKjHAeyhINuq" private val RECAPTCHA_SECRET: String? = "6Lc4TzgeAAA${"AAAjwSDqU-uG"}_Lcu2f23URMI8fq0I" private val RECAPTCHA_ENTERPRISE_SITE_KEY: String? = null } } Loading
play-services-base/src/main/java/org/microg/gms/common/api/GoogleApiManager.java +11 −3 Original line number Diff line number Diff line Loading @@ -51,7 +51,11 @@ public class GoogleApiManager { boolean connecting = client.isConnecting(); boolean connected = client.isConnected(); if (connected) { try { apiCall.execute(client, completionSource); } catch (Exception e) { completionSource.setException(e); } } else { waitingApiCallMap.get(new ApiInstance(api)).add(new WaitingApiCall<R>((PendingGoogleApiCall<R, ApiClient>) apiCall, completionSource)); if (!connecting) { Loading @@ -63,7 +67,11 @@ public class GoogleApiManager { private synchronized void onInstanceConnected(ApiInstance apiInstance, Bundle connectionHint) { List<WaitingApiCall<?>> waitingApiCalls = waitingApiCallMap.get(apiInstance); for (WaitingApiCall<?> waitingApiCall : waitingApiCalls) { try { waitingApiCall.execute(clientMap.get(apiInstance)); } catch (Exception e) { waitingApiCall.failed(e); } } waitingApiCalls.clear(); } Loading Loading @@ -120,7 +128,7 @@ public class GoogleApiManager { this.completionSource = completionSource; } public void execute(ApiClient client) { public void execute(ApiClient client) throws Exception { apiCall.execute(client, completionSource); } Loading
play-services-base/src/main/java/org/microg/gms/common/api/PendingGoogleApiCall.java +1 −1 Original line number Diff line number Diff line Loading @@ -8,5 +8,5 @@ package org.microg.gms.common.api; import com.google.android.gms.tasks.TaskCompletionSource; public interface PendingGoogleApiCall<R, A extends ApiClient> { void execute(A client, TaskCompletionSource<R> completionSource); void execute(A client, TaskCompletionSource<R> completionSource) throws Exception; }
play-services-core/build.gradle +2 −0 Original line number Diff line number Diff line Loading @@ -31,6 +31,7 @@ dependencies { implementation project(':play-services-location-core') withNearbyImplementation project(':play-services-nearby-core') implementation project(':play-services-oss-licenses-core') implementation project(':play-services-recaptcha-core') implementation project(':play-services-safetynet-core') implementation project(':play-services-tapandpay-core') implementation project(':play-services-vision-core') Loading @@ -49,6 +50,7 @@ dependencies { implementation project(':play-services-maps') implementation project(':play-services-measurement-base') implementation project(':play-services-places') implementation project(':play-services-recaptcha') implementation project(':play-services-safetynet') implementation project(':play-services-tasks-ktx') Loading
play-services-core/src/main/AndroidManifest.xml +0 −1 Original line number Diff line number Diff line Loading @@ -804,7 +804,6 @@ <action android:name="com.google.android.gms.plus.service.START" /> <action android:name="com.google.android.gms.pseudonymous.service.START" /> <action android:name="com.google.android.gms.rcs.START" /> <action android:name="com.google.android.gms.recaptcha.service.START" /> <action android:name="com.google.android.gms.romanesco.MODULE_BACKUP_AGENT" /> <action android:name="com.google.android.gms.romanesco.service.START" /> <action android:name="com.google.android.gms.search.service.SEARCH_AUTH_START" /> Loading
play-services-core/src/main/kotlin/org/microg/gms/ui/SafetyNetPreferencesFragment.kt +104 −23 Original line number Diff line number Diff line Loading @@ -9,27 +9,37 @@ import android.annotation.SuppressLint import android.os.Bundle import android.util.Base64 import android.util.Log import androidx.core.content.ContextCompat import androidx.core.os.bundleOf import androidx.navigation.fragment.findNavController import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController import androidx.preference.Preference import androidx.preference.PreferenceCategory import androidx.preference.PreferenceFragmentCompat import com.android.volley.toolbox.JsonObjectRequest import com.android.volley.toolbox.Volley import com.google.android.gms.R import com.google.android.gms.recaptcha.Recaptcha import com.google.android.gms.recaptcha.RecaptchaAction import com.google.android.gms.recaptcha.RecaptchaActionType import com.google.android.gms.safetynet.SafetyNet import com.google.android.gms.tasks.await import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.json.JSONObject import org.microg.gms.safetynet.SafetyNetDatabase import org.microg.gms.safetynet.SafetyNetRequestType import org.microg.gms.safetynet.SafetyNetRequestType.ATTESTATION import org.microg.gms.safetynet.SafetyNetRequestType.RECAPTCHA import java.net.URLEncoder import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException import kotlin.coroutines.suspendCoroutine import kotlin.random.Random class SafetyNetPreferencesFragment : PreferenceFragmentCompat() { private lateinit var runAttest: Preference private lateinit var runReCaptcha: Preference private lateinit var runReCaptchaEnterprise: Preference private lateinit var apps: PreferenceCategory private lateinit var appsAll: Preference private lateinit var appsNone: Preference Loading @@ -42,17 +52,24 @@ class SafetyNetPreferencesFragment : PreferenceFragmentCompat() { override fun onBindPreferences() { runAttest = preferenceScreen.findPreference("pref_safetynet_run_attest") ?: runAttest runReCaptcha = preferenceScreen.findPreference("pref_recaptcha_run_test") ?: runReCaptcha runReCaptchaEnterprise = preferenceScreen.findPreference("pref_recaptcha_enterprise_run_test") ?: runReCaptchaEnterprise apps = preferenceScreen.findPreference("prefcat_safetynet_apps") ?: apps appsAll = preferenceScreen.findPreference("pref_safetynet_apps_all") ?: appsAll appsNone = preferenceScreen.findPreference("pref_safetynet_apps_none") ?: appsNone runAttest.isVisible = SAFETYNET_API_KEY != null runReCaptcha.isVisible = RECAPTCHA_SITE_KEY != null runReCaptchaEnterprise.isVisible = RECAPTCHA_ENTERPRISE_SITE_KEY != null runAttest.setOnPreferenceClickListener { val context = context ?: return@setOnPreferenceClickListener false runAttest.setIcon(R.drawable.ic_circle_pending) runAttest.setSummary(R.string.pref_test_summary_running) lifecycleScope.launchWhenResumed { try { val response = SafetyNet.getClient(requireActivity()) .attest(Random.nextBytes(32), "AIzaSyCcJO6IZiA5Or_AXw3LFdaTCmpnfL4pJ-Q").await() .attest(Random.nextBytes(32), SAFETYNET_API_KEY).await() val (_, payload, _) = try { response.jwsResult.split(".") } catch (e: Exception) { Loading @@ -68,6 +85,10 @@ class SafetyNetPreferencesFragment : PreferenceFragmentCompat() { runAttest.summary = summary runAttest.icon = icon } } catch (e: Exception) { runAttest.summary = getString(R.string.pref_test_summary_failed, e.message) runAttest.icon = ContextCompat.getDrawable(context, R.drawable.ic_circle_warn) } updateContent() } true Loading @@ -77,13 +98,66 @@ class SafetyNetPreferencesFragment : PreferenceFragmentCompat() { runReCaptcha.setIcon(R.drawable.ic_circle_pending) runReCaptcha.setSummary(R.string.pref_test_summary_running) lifecycleScope.launchWhenResumed { try { val response = SafetyNet.getClient(requireActivity()) .verifyWithRecaptcha("6Lc4TzgeAAAAAJnW7Jbo6UtQ0xGuTKjHAeyhINuq").await() formatSummaryForSafetyNetResult(context, response.tokenResult, response.result.status, RECAPTCHA) .verifyWithRecaptcha(RECAPTCHA_SITE_KEY).await() val result = if (response.tokenResult != null) { val queue = Volley.newRequestQueue(context) val json = if (RECAPTCHA_SECRET != null) { suspendCoroutine { continuation -> queue.add(object : JsonObjectRequest( Method.POST, "https://www.google.com/recaptcha/api/siteverify", null, { continuation.resume(it) }, { continuation.resumeWithException(it) } ) { override fun getBodyContentType(): String = "application/x-www-form-urlencoded; charset=UTF-8" override fun getBody(): ByteArray = "secret=$RECAPTCHA_SECRET&response=${URLEncoder.encode(response.tokenResult, "UTF-8")}".encodeToByteArray() }) } } else { // Can't properly verify, everything becomes a success JSONObject(mapOf("success" to true)) } Log.d(TAG, "Result: $json") json.toString() } else { null } formatSummaryForSafetyNetResult(context, result, response.result.status, RECAPTCHA) .let { (summary, icon) -> runReCaptcha.summary = summary runReCaptcha.icon = icon } } catch (e: Exception) { runReCaptcha.summary = getString(R.string.pref_test_summary_failed, e.message) runReCaptcha.icon = ContextCompat.getDrawable(context, R.drawable.ic_circle_warn) } updateContent() } true } runReCaptchaEnterprise.setOnPreferenceClickListener { val context = context ?: return@setOnPreferenceClickListener false runReCaptchaEnterprise.setIcon(R.drawable.ic_circle_pending) runReCaptchaEnterprise.setSummary(R.string.pref_test_summary_running) lifecycleScope.launchWhenResumed { try { val client = Recaptcha.getClient(requireActivity()) val handle = client.init(RECAPTCHA_ENTERPRISE_SITE_KEY).await() val result = client.execute(handle, RecaptchaAction(RecaptchaActionType(RecaptchaActionType.SIGNUP))).await() Log.d(TAG, "Recaptcha Token: " + result.tokenResult) client.close(handle).await() runReCaptchaEnterprise.summary = getString(R.string.pref_test_summary_warn, "Incomplete Test") runReCaptchaEnterprise.icon = ContextCompat.getDrawable(context, R.drawable.ic_circle_warn) } catch (e: Exception) { runReCaptchaEnterprise.summary = getString(R.string.pref_test_summary_failed, e.message) runReCaptchaEnterprise.icon = ContextCompat.getDrawable(context, R.drawable.ic_circle_warn) } updateContent() } true Loading Loading @@ -143,4 +217,11 @@ class SafetyNetPreferencesFragment : PreferenceFragmentCompat() { } } companion object { private val SAFETYNET_API_KEY: String? = "AIzaSyCcJO6IZiA5Or_AXw3LFdaTCmpnfL4pJ-Q" private val RECAPTCHA_SITE_KEY: String? = "6Lc4TzgeAAAAAJnW7Jbo6UtQ0xGuTKjHAeyhINuq" private val RECAPTCHA_SECRET: String? = "6Lc4TzgeAAA${"AAAjwSDqU-uG"}_Lcu2f23URMI8fq0I" private val RECAPTCHA_ENTERPRISE_SITE_KEY: String? = null } }