mirror of https://github.com/YTVanced/VancedMicroG
123 lines
6.7 KiB
Kotlin
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
|
|
}
|
|
}
|
|
}
|