Loading play-services-core/src/main/kotlin/org/microg/gms/ui/SafetyNetAllAppsFragment.kt 0 → 100644 +96 −0 Original line number Diff line number Diff line /* * SPDX-FileCopyrightText: 2020, microG Project Team * SPDX-License-Identifier: Apache-2.0 */ package org.microg.gms.ui import android.os.Bundle import android.text.format.DateUtils import androidx.appcompat.content.res.AppCompatResources import androidx.core.os.bundleOf import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController import androidx.preference.Preference import androidx.preference.PreferenceCategory import androidx.preference.PreferenceFragmentCompat import com.google.android.gms.R import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.microg.gms.safetynet.SafetyNetDatabase class SafetyNetAllAppsFragment : PreferenceFragmentCompat() { private lateinit var database: SafetyNetDatabase private lateinit var apps: PreferenceCategory private lateinit var appsNone: Preference private lateinit var progress: Preference override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) database = SafetyNetDatabase(requireContext()) } override fun onResume() { super.onResume() updateContent() } override fun onPause() { super.onPause() database.close() } override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { addPreferencesFromResource(R.xml.preferences_safetynet_all_apps) apps = preferenceScreen.findPreference("prefcat_safetynet_apps_all") ?: apps appsNone = preferenceScreen.findPreference("pref_safetynet_apps_all_none") ?: appsNone progress = preferenceScreen.findPreference("pref_safetynet_apps_all_progress") ?: progress } private fun updateContent() { val context = requireContext() lifecycleScope.launchWhenResumed { val apps = withContext(Dispatchers.IO) { val res = database.recentApps.map { app -> app to context.packageManager.getApplicationInfoIfExists(app.first) }.map { (app, applicationInfo) -> val pref = AppIconPreference(context) pref.title = applicationInfo?.loadLabel(context.packageManager) ?: app.first pref.summary = when { app.second > 0 -> getString(R.string.safetynet_last_run_at, DateUtils.getRelativeTimeSpanString(app.second)) else -> null } pref.icon = applicationInfo?.loadIcon(context.packageManager) ?: AppCompatResources.getDrawable(context, android.R.mipmap.sym_def_app_icon) pref.onPreferenceClickListener = Preference.OnPreferenceClickListener { findNavController().navigate( requireContext(), R.id.openSafetyNetAppDetailsFromAll, bundleOf( "package" to app.first ) ) true } pref.key = "pref_safetynet_app_" + app.first pref }.sortedBy { it.title.toString().lowercase() } database.close() res } this@SafetyNetAllAppsFragment.apps.removeAll() this@SafetyNetAllAppsFragment.apps.isVisible = true var hadRegistered = false var hadUnregistered = false for (app in apps) { this@SafetyNetAllAppsFragment.apps.addPreference(app) } appsNone.isVisible = apps.isEmpty() if (apps.isEmpty()) this@SafetyNetAllAppsFragment.apps.addPreference(appsNone) progress.isVisible = false } } } play-services-core/src/main/kotlin/org/microg/gms/ui/SafetyNetAppFragment.kt 0 → 100644 +63 −0 Original line number Diff line number Diff line /* * SPDX-FileCopyrightText: 2020, microG Project Team * SPDX-License-Identifier: Apache-2.0 */ package org.microg.gms.ui import android.content.Intent import android.net.Uri import android.os.Bundle import android.provider.Settings import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.appcompat.content.res.AppCompatResources import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope import com.google.android.gms.R import com.google.android.gms.databinding.PushNotificationAppFragmentBinding import com.google.android.gms.databinding.SafetyNetAppFragmentBinding class SafetyNetAppFragment : Fragment(R.layout.safety_net_app_fragment) { lateinit var binding: SafetyNetAppFragmentBinding val packageName: String? get() = arguments?.getString("package") override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { binding = SafetyNetAppFragmentBinding.inflate(inflater, container, false) binding.callbacks = object : SafetyNetAppFragmentCallbacks { override fun onAppClicked() { val intent = Intent() intent.action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS val uri: Uri = Uri.fromParts("package", packageName, null) intent.data = uri try { requireContext().startActivity(intent) } catch (e: Exception) { Log.w(TAG, "Failed to launch app", e) } } } childFragmentManager.findFragmentById(R.id.sub_preferences)?.arguments = arguments return binding.root } override fun onResume() { super.onResume() val context = requireContext() lifecycleScope.launchWhenResumed { val pm = context.packageManager val applicationInfo = pm.getApplicationInfoIfExists(packageName) binding.appName = applicationInfo?.loadLabel(pm)?.toString() ?: packageName binding.appIcon = applicationInfo?.loadIcon(pm) ?: AppCompatResources.getDrawable(context, android.R.mipmap.sym_def_app_icon) } } } interface SafetyNetAppFragmentCallbacks { fun onAppClicked() } play-services-core/src/main/kotlin/org/microg/gms/ui/SafetyNetAppPreferencesFragment.kt 0 → 100644 +99 −0 Original line number Diff line number Diff line /* * SPDX-FileCopyrightText: 2020, microG Project Team * SPDX-License-Identifier: Apache-2.0 */ package org.microg.gms.ui import android.annotation.SuppressLint import android.os.Bundle import android.text.format.DateUtils import android.util.Base64 import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.TextView import androidx.appcompat.app.AlertDialog import androidx.core.view.ViewCompat import androidx.fragment.app.DialogFragment import androidx.fragment.app.FragmentContainerView import androidx.fragment.app.add import androidx.fragment.app.commit import androidx.lifecycle.lifecycleScope import androidx.preference.* import com.google.android.gms.R import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.json.JSONObject import org.microg.gms.gcm.GcmDatabase import org.microg.gms.gcm.PushRegisterManager 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 class SafetyNetAppPreferencesFragment : PreferenceFragmentCompat() { private lateinit var recents: PreferenceCategory private lateinit var recentsNone: Preference private val packageName: String? get() = arguments?.getString("package") override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { addPreferencesFromResource(R.xml.preferences_safetynet_app) } @SuppressLint("RestrictedApi") override fun onBindPreferences() { recents = preferenceScreen.findPreference("prefcat_safetynet_recent_list") ?: recents recentsNone = preferenceScreen.findPreference("pref_safetynet_recent_none") ?: recentsNone } override fun onResume() { super.onResume() updateContent() } fun updateContent() { lifecycleScope.launchWhenResumed { val context = requireContext() val summaries = packageName?.let { packageName -> SafetyNetDatabase(context).use { it.getRecentRequests(packageName) } } .orEmpty() recents.removeAll() recents.addPreference(recentsNone) recentsNone.isVisible = summaries.isEmpty() for (summary in summaries) { val preference = Preference(requireContext()) preference.onPreferenceClickListener = Preference.OnPreferenceClickListener { SafetyNetRecentDialogFragment().apply { arguments = Bundle().apply { putParcelable("summary", summary) } }.show(requireFragmentManager(), null) true } val date = DateUtils.getRelativeDateTimeString( context, summary.timestamp, DateUtils.MINUTE_IN_MILLIS, DateUtils.WEEK_IN_MILLIS, DateUtils.FORMAT_SHOW_TIME ) preference.title = date formatSummaryForSafetyNetResult( context, summary.responseData, summary.responseStatus, summary.requestType ).let { (text, icon) -> preference.summary = when (summary.requestType) { ATTESTATION -> "Attestation: $text" RECAPTCHA -> "ReCaptcha: $text" } preference.icon = icon } recents.addPreference(preference) } } } } play-services-core/src/main/kotlin/org/microg/gms/ui/SafetyNetFragment.kt +10 −0 Original line number Diff line number Diff line Loading @@ -14,6 +14,7 @@ import com.google.android.gms.R import com.google.android.gms.databinding.SafetyNetFragmentBinding import org.microg.gms.checkin.CheckinPrefs import org.microg.gms.droidguard.core.DroidGuardPreferences import org.microg.gms.safetynet.SafetyNetDatabase import org.microg.gms.safetynet.SafetyNetPreferences class SafetyNetFragment : Fragment(R.layout.safety_net_fragment) { Loading Loading @@ -58,6 +59,7 @@ class SafetyNetFragment : Fragment(R.layout.safety_net_fragment) { override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { menu.add(0, MENU_ADVANCED, 0, R.string.menu_advanced) menu.add(0, MENU_CLEAR_REQUESTS, 0, R.string.menu_clear_recent_requests) super.onCreateOptionsMenu(menu, inflater) } Loading @@ -67,11 +69,19 @@ class SafetyNetFragment : Fragment(R.layout.safety_net_fragment) { findNavController().navigate(requireContext(), R.id.openSafetyNetAdvancedSettings) true } MENU_CLEAR_REQUESTS -> { val db = SafetyNetDatabase(requireContext()) db.clearAllRequests() db.close() (childFragmentManager.findFragmentById(R.id.sub_preferences) as? SafetyNetPreferencesFragment)?.updateContent() true } else -> super.onOptionsItemSelected(item) } } companion object { private const val MENU_ADVANCED = Menu.FIRST private const val MENU_CLEAR_REQUESTS = Menu.FIRST + 1 } } play-services-core/src/main/kotlin/org/microg/gms/ui/SafetyNetPreferencesFragment.kt +85 −79 Original line number Diff line number Diff line Loading @@ -7,28 +7,32 @@ package org.microg.gms.ui import android.annotation.SuppressLint import android.os.Bundle import android.os.Handler import android.os.Looper import android.util.Base64 import android.util.Log import androidx.core.os.bundleOf import androidx.navigation.fragment.findNavController import androidx.lifecycle.lifecycleScope import androidx.preference.Preference import androidx.preference.PreferenceCategory import androidx.preference.PreferenceFragmentCompat import com.google.android.gms.R import com.google.android.gms.common.api.Status import com.google.android.gms.safetynet.RecaptchaResultData import com.google.android.gms.safetynet.SafetyNet import com.google.android.gms.safetynet.internal.ISafetyNetCallbacks import com.google.android.gms.tasks.await import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.json.JSONObject import org.microg.gms.safetynet.SafetyNetClientServiceImpl 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 kotlin.random.Random class SafetyNetPreferencesFragment : PreferenceFragmentCompat() { private lateinit var runAttest: Preference private lateinit var runReCaptcha: Preference private lateinit var seeRecent: Preference private lateinit var apps: PreferenceCategory private lateinit var appsAll: Preference private lateinit var appsNone: Preference override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { addPreferencesFromResource(R.xml.preferences_safetynet) Loading @@ -36,11 +40,12 @@ class SafetyNetPreferencesFragment : PreferenceFragmentCompat() { @SuppressLint("RestrictedApi") override fun onBindPreferences() { runAttest = preferenceScreen.findPreference("pref_snet_run_attest") ?: runAttest runAttest = preferenceScreen.findPreference("pref_safetynet_run_attest") ?: runAttest runReCaptcha = preferenceScreen.findPreference("pref_recaptcha_run_test") ?: runReCaptcha seeRecent = preferenceScreen.findPreference("pref_snet_recent") ?: seeRecent apps = preferenceScreen.findPreference("prefcat_safetynet_apps") ?: apps appsAll = preferenceScreen.findPreference("pref_safetynet_apps_all") ?: appsAll appsNone = preferenceScreen.findPreference("pref_safetynet_apps_none") ?: appsNone // TODO: Use SafetyNet client library once ready runAttest.setOnPreferenceClickListener { val context = context ?: return@setOnPreferenceClickListener false runAttest.setIcon(R.drawable.ic_circle_pending) Loading @@ -48,65 +53,22 @@ class SafetyNetPreferencesFragment : PreferenceFragmentCompat() { lifecycleScope.launchWhenResumed { val response = SafetyNet.getClient(requireActivity()) .attest(Random.nextBytes(32), "AIzaSyCcJO6IZiA5Or_AXw3LFdaTCmpnfL4pJ-Q").await() if (response.result.status?.isSuccess == true) { if (response.jwsResult == null) { runAttest.setIcon(R.drawable.ic_circle_warn) runAttest.summary = context.getString(R.string.pref_test_summary_failed, "No result") } else { val (_, payload, _) = try { response.jwsResult.split(".") } catch (e: Exception) { runAttest.setIcon(R.drawable.ic_circle_error) runAttest.summary = context.getString(R.string.pref_test_summary_failed, "Invalid JWS") return@launchWhenResumed } val (basicIntegrity, ctsProfileMatch, advice) = try { JSONObject(Base64.decode(payload, Base64.URL_SAFE).decodeToString()).let { Triple( it.optBoolean("basicIntegrity", false), it.optBoolean("ctsProfileMatch", false), it.optString("advice", "") ) } } catch (e: Exception) { Log.w(TAG, e) runAttest.setIcon(R.drawable.ic_circle_error) runAttest.summary = context.getString(R.string.pref_test_summary_failed, "Invalid JSON") return@launchWhenResumed } val adviceText = if (advice == "") "" else "\n" + advice.split(",").map { when (it) { "LOCK_BOOTLOADER" -> "Bootloader is not locked" "RESTORE_TO_FACTORY_ROM" -> "ROM is not clean" else -> it } }.joinToString("\n") when { basicIntegrity && ctsProfileMatch -> { runAttest.setIcon(R.drawable.ic_circle_check) runAttest.setSummary(R.string.pref_test_summary_passed) } basicIntegrity -> { runAttest.setIcon(R.drawable.ic_circle_warn) runAttest.summary = context.getString( R.string.pref_test_summary_warn, "CTS profile does not match$adviceText" ) listOf(null, null, null) } else -> { runAttest.setIcon(R.drawable.ic_circle_error) runAttest.summary = context.getString( R.string.pref_test_summary_failed, "integrity check failed$adviceText" formatSummaryForSafetyNetResult( context, Base64.decode(payload, Base64.URL_SAFE).decodeToString(), response.result.status, ATTESTATION ) .let { (summary, icon) -> runAttest.summary = summary runAttest.icon = icon } } } } else { runAttest.setIcon(R.drawable.ic_circle_error) runAttest.summary = context.getString(R.string.pref_test_summary_failed, response.result.status?.statusMessage) } updateContent() } true } Loading @@ -115,21 +77,65 @@ class SafetyNetPreferencesFragment : PreferenceFragmentCompat() { runReCaptcha.setIcon(R.drawable.ic_circle_pending) runReCaptcha.setSummary(R.string.pref_test_summary_running) lifecycleScope.launchWhenResumed { val response = SafetyNet.getClient(requireActivity()).verifyWithRecaptcha("6Lc4TzgeAAAAAJnW7Jbo6UtQ0xGuTKjHAeyhINuq").await() if (response.result.status?.isSuccess == true) { runReCaptcha.setIcon(R.drawable.ic_circle_check) runReCaptcha.setSummary(R.string.pref_test_summary_passed) } else { runReCaptcha.setIcon(R.drawable.ic_circle_error) runReCaptcha.summary = context.getString(R.string.pref_test_summary_failed, response.result.status?.statusMessage) val response = SafetyNet.getClient(requireActivity()) .verifyWithRecaptcha("6Lc4TzgeAAAAAJnW7Jbo6UtQ0xGuTKjHAeyhINuq").await() formatSummaryForSafetyNetResult(context, response.tokenResult, response.result.status, RECAPTCHA) .let { (summary, icon) -> runReCaptcha.summary = summary runReCaptcha.icon = icon } updateContent() } true } seeRecent.setOnPreferenceClickListener { findNavController().navigate(requireContext(), R.id.openSafetyNetRecentList) appsAll.setOnPreferenceClickListener { findNavController().navigate(requireContext(), R.id.openAllSafetyNetApps) true } } override fun onResume() { super.onResume() updateContent() } fun updateContent() { lifecycleScope.launchWhenResumed { val context = requireContext() val (apps, showAll) = withContext(Dispatchers.IO) { val apps = SafetyNetDatabase(requireContext()).use { it.recentApps } apps.map { app -> app to context.packageManager.getApplicationInfoIfExists(app.first) }.mapNotNull { (app, info) -> if (info == null) null else app to info }.take(3).mapIndexed { idx, (app, applicationInfo) -> val pref = AppIconPreference(context) pref.order = idx pref.title = applicationInfo.loadLabel(context.packageManager) pref.icon = applicationInfo.loadIcon(context.packageManager) pref.onPreferenceClickListener = Preference.OnPreferenceClickListener { findNavController().navigate( requireContext(), R.id.openSafetyNetAppDetails, bundleOf( "package" to app.first ) ) true } pref.key = "pref_safetynet_app_" + app.first pref }.let { it to (it.size < apps.size) } } appsAll.isVisible = showAll this@SafetyNetPreferencesFragment.apps.removeAll() for (app in apps) { this@SafetyNetPreferencesFragment.apps.addPreference(app) } if (showAll) { this@SafetyNetPreferencesFragment.apps.addPreference(appsAll) } else if (apps.isEmpty()) { this@SafetyNetPreferencesFragment.apps.addPreference(appsNone) } } } } Loading
play-services-core/src/main/kotlin/org/microg/gms/ui/SafetyNetAllAppsFragment.kt 0 → 100644 +96 −0 Original line number Diff line number Diff line /* * SPDX-FileCopyrightText: 2020, microG Project Team * SPDX-License-Identifier: Apache-2.0 */ package org.microg.gms.ui import android.os.Bundle import android.text.format.DateUtils import androidx.appcompat.content.res.AppCompatResources import androidx.core.os.bundleOf import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController import androidx.preference.Preference import androidx.preference.PreferenceCategory import androidx.preference.PreferenceFragmentCompat import com.google.android.gms.R import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.microg.gms.safetynet.SafetyNetDatabase class SafetyNetAllAppsFragment : PreferenceFragmentCompat() { private lateinit var database: SafetyNetDatabase private lateinit var apps: PreferenceCategory private lateinit var appsNone: Preference private lateinit var progress: Preference override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) database = SafetyNetDatabase(requireContext()) } override fun onResume() { super.onResume() updateContent() } override fun onPause() { super.onPause() database.close() } override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { addPreferencesFromResource(R.xml.preferences_safetynet_all_apps) apps = preferenceScreen.findPreference("prefcat_safetynet_apps_all") ?: apps appsNone = preferenceScreen.findPreference("pref_safetynet_apps_all_none") ?: appsNone progress = preferenceScreen.findPreference("pref_safetynet_apps_all_progress") ?: progress } private fun updateContent() { val context = requireContext() lifecycleScope.launchWhenResumed { val apps = withContext(Dispatchers.IO) { val res = database.recentApps.map { app -> app to context.packageManager.getApplicationInfoIfExists(app.first) }.map { (app, applicationInfo) -> val pref = AppIconPreference(context) pref.title = applicationInfo?.loadLabel(context.packageManager) ?: app.first pref.summary = when { app.second > 0 -> getString(R.string.safetynet_last_run_at, DateUtils.getRelativeTimeSpanString(app.second)) else -> null } pref.icon = applicationInfo?.loadIcon(context.packageManager) ?: AppCompatResources.getDrawable(context, android.R.mipmap.sym_def_app_icon) pref.onPreferenceClickListener = Preference.OnPreferenceClickListener { findNavController().navigate( requireContext(), R.id.openSafetyNetAppDetailsFromAll, bundleOf( "package" to app.first ) ) true } pref.key = "pref_safetynet_app_" + app.first pref }.sortedBy { it.title.toString().lowercase() } database.close() res } this@SafetyNetAllAppsFragment.apps.removeAll() this@SafetyNetAllAppsFragment.apps.isVisible = true var hadRegistered = false var hadUnregistered = false for (app in apps) { this@SafetyNetAllAppsFragment.apps.addPreference(app) } appsNone.isVisible = apps.isEmpty() if (apps.isEmpty()) this@SafetyNetAllAppsFragment.apps.addPreference(appsNone) progress.isVisible = false } } }
play-services-core/src/main/kotlin/org/microg/gms/ui/SafetyNetAppFragment.kt 0 → 100644 +63 −0 Original line number Diff line number Diff line /* * SPDX-FileCopyrightText: 2020, microG Project Team * SPDX-License-Identifier: Apache-2.0 */ package org.microg.gms.ui import android.content.Intent import android.net.Uri import android.os.Bundle import android.provider.Settings import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.appcompat.content.res.AppCompatResources import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope import com.google.android.gms.R import com.google.android.gms.databinding.PushNotificationAppFragmentBinding import com.google.android.gms.databinding.SafetyNetAppFragmentBinding class SafetyNetAppFragment : Fragment(R.layout.safety_net_app_fragment) { lateinit var binding: SafetyNetAppFragmentBinding val packageName: String? get() = arguments?.getString("package") override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { binding = SafetyNetAppFragmentBinding.inflate(inflater, container, false) binding.callbacks = object : SafetyNetAppFragmentCallbacks { override fun onAppClicked() { val intent = Intent() intent.action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS val uri: Uri = Uri.fromParts("package", packageName, null) intent.data = uri try { requireContext().startActivity(intent) } catch (e: Exception) { Log.w(TAG, "Failed to launch app", e) } } } childFragmentManager.findFragmentById(R.id.sub_preferences)?.arguments = arguments return binding.root } override fun onResume() { super.onResume() val context = requireContext() lifecycleScope.launchWhenResumed { val pm = context.packageManager val applicationInfo = pm.getApplicationInfoIfExists(packageName) binding.appName = applicationInfo?.loadLabel(pm)?.toString() ?: packageName binding.appIcon = applicationInfo?.loadIcon(pm) ?: AppCompatResources.getDrawable(context, android.R.mipmap.sym_def_app_icon) } } } interface SafetyNetAppFragmentCallbacks { fun onAppClicked() }
play-services-core/src/main/kotlin/org/microg/gms/ui/SafetyNetAppPreferencesFragment.kt 0 → 100644 +99 −0 Original line number Diff line number Diff line /* * SPDX-FileCopyrightText: 2020, microG Project Team * SPDX-License-Identifier: Apache-2.0 */ package org.microg.gms.ui import android.annotation.SuppressLint import android.os.Bundle import android.text.format.DateUtils import android.util.Base64 import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.TextView import androidx.appcompat.app.AlertDialog import androidx.core.view.ViewCompat import androidx.fragment.app.DialogFragment import androidx.fragment.app.FragmentContainerView import androidx.fragment.app.add import androidx.fragment.app.commit import androidx.lifecycle.lifecycleScope import androidx.preference.* import com.google.android.gms.R import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.json.JSONObject import org.microg.gms.gcm.GcmDatabase import org.microg.gms.gcm.PushRegisterManager 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 class SafetyNetAppPreferencesFragment : PreferenceFragmentCompat() { private lateinit var recents: PreferenceCategory private lateinit var recentsNone: Preference private val packageName: String? get() = arguments?.getString("package") override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { addPreferencesFromResource(R.xml.preferences_safetynet_app) } @SuppressLint("RestrictedApi") override fun onBindPreferences() { recents = preferenceScreen.findPreference("prefcat_safetynet_recent_list") ?: recents recentsNone = preferenceScreen.findPreference("pref_safetynet_recent_none") ?: recentsNone } override fun onResume() { super.onResume() updateContent() } fun updateContent() { lifecycleScope.launchWhenResumed { val context = requireContext() val summaries = packageName?.let { packageName -> SafetyNetDatabase(context).use { it.getRecentRequests(packageName) } } .orEmpty() recents.removeAll() recents.addPreference(recentsNone) recentsNone.isVisible = summaries.isEmpty() for (summary in summaries) { val preference = Preference(requireContext()) preference.onPreferenceClickListener = Preference.OnPreferenceClickListener { SafetyNetRecentDialogFragment().apply { arguments = Bundle().apply { putParcelable("summary", summary) } }.show(requireFragmentManager(), null) true } val date = DateUtils.getRelativeDateTimeString( context, summary.timestamp, DateUtils.MINUTE_IN_MILLIS, DateUtils.WEEK_IN_MILLIS, DateUtils.FORMAT_SHOW_TIME ) preference.title = date formatSummaryForSafetyNetResult( context, summary.responseData, summary.responseStatus, summary.requestType ).let { (text, icon) -> preference.summary = when (summary.requestType) { ATTESTATION -> "Attestation: $text" RECAPTCHA -> "ReCaptcha: $text" } preference.icon = icon } recents.addPreference(preference) } } } }
play-services-core/src/main/kotlin/org/microg/gms/ui/SafetyNetFragment.kt +10 −0 Original line number Diff line number Diff line Loading @@ -14,6 +14,7 @@ import com.google.android.gms.R import com.google.android.gms.databinding.SafetyNetFragmentBinding import org.microg.gms.checkin.CheckinPrefs import org.microg.gms.droidguard.core.DroidGuardPreferences import org.microg.gms.safetynet.SafetyNetDatabase import org.microg.gms.safetynet.SafetyNetPreferences class SafetyNetFragment : Fragment(R.layout.safety_net_fragment) { Loading Loading @@ -58,6 +59,7 @@ class SafetyNetFragment : Fragment(R.layout.safety_net_fragment) { override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { menu.add(0, MENU_ADVANCED, 0, R.string.menu_advanced) menu.add(0, MENU_CLEAR_REQUESTS, 0, R.string.menu_clear_recent_requests) super.onCreateOptionsMenu(menu, inflater) } Loading @@ -67,11 +69,19 @@ class SafetyNetFragment : Fragment(R.layout.safety_net_fragment) { findNavController().navigate(requireContext(), R.id.openSafetyNetAdvancedSettings) true } MENU_CLEAR_REQUESTS -> { val db = SafetyNetDatabase(requireContext()) db.clearAllRequests() db.close() (childFragmentManager.findFragmentById(R.id.sub_preferences) as? SafetyNetPreferencesFragment)?.updateContent() true } else -> super.onOptionsItemSelected(item) } } companion object { private const val MENU_ADVANCED = Menu.FIRST private const val MENU_CLEAR_REQUESTS = Menu.FIRST + 1 } }
play-services-core/src/main/kotlin/org/microg/gms/ui/SafetyNetPreferencesFragment.kt +85 −79 Original line number Diff line number Diff line Loading @@ -7,28 +7,32 @@ package org.microg.gms.ui import android.annotation.SuppressLint import android.os.Bundle import android.os.Handler import android.os.Looper import android.util.Base64 import android.util.Log import androidx.core.os.bundleOf import androidx.navigation.fragment.findNavController import androidx.lifecycle.lifecycleScope import androidx.preference.Preference import androidx.preference.PreferenceCategory import androidx.preference.PreferenceFragmentCompat import com.google.android.gms.R import com.google.android.gms.common.api.Status import com.google.android.gms.safetynet.RecaptchaResultData import com.google.android.gms.safetynet.SafetyNet import com.google.android.gms.safetynet.internal.ISafetyNetCallbacks import com.google.android.gms.tasks.await import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.json.JSONObject import org.microg.gms.safetynet.SafetyNetClientServiceImpl 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 kotlin.random.Random class SafetyNetPreferencesFragment : PreferenceFragmentCompat() { private lateinit var runAttest: Preference private lateinit var runReCaptcha: Preference private lateinit var seeRecent: Preference private lateinit var apps: PreferenceCategory private lateinit var appsAll: Preference private lateinit var appsNone: Preference override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { addPreferencesFromResource(R.xml.preferences_safetynet) Loading @@ -36,11 +40,12 @@ class SafetyNetPreferencesFragment : PreferenceFragmentCompat() { @SuppressLint("RestrictedApi") override fun onBindPreferences() { runAttest = preferenceScreen.findPreference("pref_snet_run_attest") ?: runAttest runAttest = preferenceScreen.findPreference("pref_safetynet_run_attest") ?: runAttest runReCaptcha = preferenceScreen.findPreference("pref_recaptcha_run_test") ?: runReCaptcha seeRecent = preferenceScreen.findPreference("pref_snet_recent") ?: seeRecent apps = preferenceScreen.findPreference("prefcat_safetynet_apps") ?: apps appsAll = preferenceScreen.findPreference("pref_safetynet_apps_all") ?: appsAll appsNone = preferenceScreen.findPreference("pref_safetynet_apps_none") ?: appsNone // TODO: Use SafetyNet client library once ready runAttest.setOnPreferenceClickListener { val context = context ?: return@setOnPreferenceClickListener false runAttest.setIcon(R.drawable.ic_circle_pending) Loading @@ -48,65 +53,22 @@ class SafetyNetPreferencesFragment : PreferenceFragmentCompat() { lifecycleScope.launchWhenResumed { val response = SafetyNet.getClient(requireActivity()) .attest(Random.nextBytes(32), "AIzaSyCcJO6IZiA5Or_AXw3LFdaTCmpnfL4pJ-Q").await() if (response.result.status?.isSuccess == true) { if (response.jwsResult == null) { runAttest.setIcon(R.drawable.ic_circle_warn) runAttest.summary = context.getString(R.string.pref_test_summary_failed, "No result") } else { val (_, payload, _) = try { response.jwsResult.split(".") } catch (e: Exception) { runAttest.setIcon(R.drawable.ic_circle_error) runAttest.summary = context.getString(R.string.pref_test_summary_failed, "Invalid JWS") return@launchWhenResumed } val (basicIntegrity, ctsProfileMatch, advice) = try { JSONObject(Base64.decode(payload, Base64.URL_SAFE).decodeToString()).let { Triple( it.optBoolean("basicIntegrity", false), it.optBoolean("ctsProfileMatch", false), it.optString("advice", "") ) } } catch (e: Exception) { Log.w(TAG, e) runAttest.setIcon(R.drawable.ic_circle_error) runAttest.summary = context.getString(R.string.pref_test_summary_failed, "Invalid JSON") return@launchWhenResumed } val adviceText = if (advice == "") "" else "\n" + advice.split(",").map { when (it) { "LOCK_BOOTLOADER" -> "Bootloader is not locked" "RESTORE_TO_FACTORY_ROM" -> "ROM is not clean" else -> it } }.joinToString("\n") when { basicIntegrity && ctsProfileMatch -> { runAttest.setIcon(R.drawable.ic_circle_check) runAttest.setSummary(R.string.pref_test_summary_passed) } basicIntegrity -> { runAttest.setIcon(R.drawable.ic_circle_warn) runAttest.summary = context.getString( R.string.pref_test_summary_warn, "CTS profile does not match$adviceText" ) listOf(null, null, null) } else -> { runAttest.setIcon(R.drawable.ic_circle_error) runAttest.summary = context.getString( R.string.pref_test_summary_failed, "integrity check failed$adviceText" formatSummaryForSafetyNetResult( context, Base64.decode(payload, Base64.URL_SAFE).decodeToString(), response.result.status, ATTESTATION ) .let { (summary, icon) -> runAttest.summary = summary runAttest.icon = icon } } } } else { runAttest.setIcon(R.drawable.ic_circle_error) runAttest.summary = context.getString(R.string.pref_test_summary_failed, response.result.status?.statusMessage) } updateContent() } true } Loading @@ -115,21 +77,65 @@ class SafetyNetPreferencesFragment : PreferenceFragmentCompat() { runReCaptcha.setIcon(R.drawable.ic_circle_pending) runReCaptcha.setSummary(R.string.pref_test_summary_running) lifecycleScope.launchWhenResumed { val response = SafetyNet.getClient(requireActivity()).verifyWithRecaptcha("6Lc4TzgeAAAAAJnW7Jbo6UtQ0xGuTKjHAeyhINuq").await() if (response.result.status?.isSuccess == true) { runReCaptcha.setIcon(R.drawable.ic_circle_check) runReCaptcha.setSummary(R.string.pref_test_summary_passed) } else { runReCaptcha.setIcon(R.drawable.ic_circle_error) runReCaptcha.summary = context.getString(R.string.pref_test_summary_failed, response.result.status?.statusMessage) val response = SafetyNet.getClient(requireActivity()) .verifyWithRecaptcha("6Lc4TzgeAAAAAJnW7Jbo6UtQ0xGuTKjHAeyhINuq").await() formatSummaryForSafetyNetResult(context, response.tokenResult, response.result.status, RECAPTCHA) .let { (summary, icon) -> runReCaptcha.summary = summary runReCaptcha.icon = icon } updateContent() } true } seeRecent.setOnPreferenceClickListener { findNavController().navigate(requireContext(), R.id.openSafetyNetRecentList) appsAll.setOnPreferenceClickListener { findNavController().navigate(requireContext(), R.id.openAllSafetyNetApps) true } } override fun onResume() { super.onResume() updateContent() } fun updateContent() { lifecycleScope.launchWhenResumed { val context = requireContext() val (apps, showAll) = withContext(Dispatchers.IO) { val apps = SafetyNetDatabase(requireContext()).use { it.recentApps } apps.map { app -> app to context.packageManager.getApplicationInfoIfExists(app.first) }.mapNotNull { (app, info) -> if (info == null) null else app to info }.take(3).mapIndexed { idx, (app, applicationInfo) -> val pref = AppIconPreference(context) pref.order = idx pref.title = applicationInfo.loadLabel(context.packageManager) pref.icon = applicationInfo.loadIcon(context.packageManager) pref.onPreferenceClickListener = Preference.OnPreferenceClickListener { findNavController().navigate( requireContext(), R.id.openSafetyNetAppDetails, bundleOf( "package" to app.first ) ) true } pref.key = "pref_safetynet_app_" + app.first pref }.let { it to (it.size < apps.size) } } appsAll.isVisible = showAll this@SafetyNetPreferencesFragment.apps.removeAll() for (app in apps) { this@SafetyNetPreferencesFragment.apps.addPreference(app) } if (showAll) { this@SafetyNetPreferencesFragment.apps.addPreference(appsAll) } else if (apps.isEmpty()) { this@SafetyNetPreferencesFragment.apps.addPreference(appsNone) } } } }