VancedMicroG/play-services-core/src/main/kotlin/org/microg/gms/ui/SafetyNetPreferencesFragmen...

123 lines
6.7 KiB
Kotlin

/*
* SPDX-FileCopyrightText: 2020, microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package org.microg.gms.ui
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.util.Base64
import android.util.Log
import androidx.lifecycle.lifecycleScope
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import com.google.android.gms.R
import com.google.android.gms.common.api.Status
import com.google.android.gms.safetynet.AttestationData
import com.google.android.gms.safetynet.RecaptchaResultData
import com.google.android.gms.safetynet.internal.ISafetyNetCallbacks
import org.json.JSONException
import org.json.JSONObject
import org.microg.gms.safetynet.SafetyNetClientService
import org.microg.gms.safetynet.SafetyNetClientServiceImpl
import kotlin.random.Random
class SafetyNetPreferencesFragment : PreferenceFragmentCompat() {
private lateinit var runAttest: Preference
private lateinit var runReCaptcha: Preference
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.preferences_safetynet)
}
override fun onBindPreferences() {
runAttest = preferenceScreen.findPreference("pref_snet_run_attest") ?: runAttest
runReCaptcha = preferenceScreen.findPreference("pref_recaptcha_run_test") ?: runReCaptcha
// TODO: Use SafetyNet client library once ready
runAttest.setOnPreferenceClickListener {
val context = context ?: return@setOnPreferenceClickListener false
runAttest.setIcon(R.drawable.ic_circle_pending)
runAttest.setSummary(R.string.pref_test_summary_running)
val handler = Handler(Looper.myLooper()!!)
SafetyNetClientServiceImpl(context, context.packageName, lifecycle).attestWithApiKey(object : ISafetyNetCallbacks.Default() {
override fun onAttestationData(status: Status?, attestationData: AttestationData?) {
handler.post {
if (status?.isSuccess == true) {
if (attestationData?.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 {
attestationData.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@post
}
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@post
}
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")
}
else -> {
runAttest.setIcon(R.drawable.ic_circle_error)
runAttest.summary = context.getString(R.string.pref_test_summary_failed, "integrity check failed$adviceText")
}
}
}
} else {
runAttest.setIcon(R.drawable.ic_circle_error)
runAttest.summary = context.getString(R.string.pref_test_summary_failed, status?.statusMessage)
}
}
}
}, Random.nextBytes(32), "AIzaSyCcJO6IZiA5Or_AXw3LFdaTCmpnfL4pJ-Q")
true
}
runReCaptcha.setOnPreferenceClickListener {
val context = context ?: return@setOnPreferenceClickListener false
runReCaptcha.setIcon(R.drawable.ic_circle_pending)
runReCaptcha.setSummary(R.string.pref_test_summary_running)
val handler = Handler(Looper.myLooper()!!)
SafetyNetClientServiceImpl(context, context.packageName, lifecycle).verifyWithRecaptcha(object : ISafetyNetCallbacks.Default() {
override fun onRecaptchaResult(status: Status?, recaptchaResultData: RecaptchaResultData?) {
handler.post {
if (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, status?.statusMessage)
}
}
}
}, "6Lc4TzgeAAAAAJnW7Jbo6UtQ0xGuTKjHAeyhINuq")
true
}
}
}