diff --git a/app/shared/src/main/AndroidManifest.xml b/app/shared/src/main/AndroidManifest.xml
index de79e1751..096449b52 100644
--- a/app/shared/src/main/AndroidManifest.xml
+++ b/app/shared/src/main/AndroidManifest.xml
@@ -25,9 +25,4 @@
android:usesCleartextTraffic="true"
tools:ignore="UnusedAttribute" />
-
-
-
diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/home/HomeViewModel.kt b/app/src/main/java/com/topjohnwu/magisk/ui/home/HomeViewModel.kt
index 228770215..a78cb4134 100644
--- a/app/src/main/java/com/topjohnwu/magisk/ui/home/HomeViewModel.kt
+++ b/app/src/main/java/com/topjohnwu/magisk/ui/home/HomeViewModel.kt
@@ -75,9 +75,6 @@ class HomeViewModel(
var stateManagerProgress = 0
set(value) = set(value, field, { field = it }, BR.stateManagerProgress)
- @get:Bindable
- val showSafetyNet get() = Info.hasGMS && isConnected.get()
-
val itemBinding = itemBindingOf {
it.bindExtra(BR.viewModel, this)
}
@@ -86,7 +83,6 @@ class HomeViewModel(
override fun refresh() = viewModelScope.launch {
state = State.LOADING
- notifyPropertyChanged(BR.showSafetyNet)
Info.getRemote(svc)?.apply {
state = State.LOADED
@@ -101,10 +97,10 @@ class HomeViewModel(
launch {
ensureEnv()
}
- } ?: {
+ } ?: run {
state = State.LOADING_FAILED
managerRemoteVersion = R.string.not_available.asText()
- }()
+ }
}
val showTest = false
@@ -134,9 +130,6 @@ class HomeViewModel(
HomeFragmentDirections.actionHomeFragmentToInstallFragment().navigate()
}
- fun onSafetyNetPressed() =
- HomeFragmentDirections.actionHomeFragmentToSafetynetFragment().navigate()
-
fun hideNotice() {
Config.safetyNotice = false
isNoticeVisible = false
diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/safetynet/CheckSafetyNetEvent.kt b/app/src/main/java/com/topjohnwu/magisk/ui/safetynet/CheckSafetyNetEvent.kt
deleted file mode 100644
index 30035ddb2..000000000
--- a/app/src/main/java/com/topjohnwu/magisk/ui/safetynet/CheckSafetyNetEvent.kt
+++ /dev/null
@@ -1,225 +0,0 @@
-@file:Suppress("DEPRECATION")
-
-package com.topjohnwu.magisk.ui.safetynet
-
-import android.content.Context
-import android.util.Base64
-import com.squareup.moshi.Json
-import com.squareup.moshi.JsonClass
-import com.squareup.moshi.Moshi
-import com.topjohnwu.magisk.R
-import com.topjohnwu.magisk.arch.ContextExecutor
-import com.topjohnwu.magisk.arch.ViewEventWithScope
-import com.topjohnwu.magisk.core.Const
-import com.topjohnwu.magisk.di.ServiceLocator
-import com.topjohnwu.magisk.ktx.createClassLoader
-import com.topjohnwu.magisk.ktx.reflectField
-import com.topjohnwu.magisk.ktx.writeTo
-import com.topjohnwu.magisk.signing.CryptoUtils
-import com.topjohnwu.magisk.view.MagiskDialog
-import com.topjohnwu.superuser.Shell
-import dalvik.system.BaseDexClassLoader
-import dalvik.system.DexFile
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
-import org.bouncycastle.asn1.ASN1Encoding
-import org.bouncycastle.asn1.ASN1Primitive
-import org.bouncycastle.est.jcajce.JsseDefaultHostnameAuthorizer
-import timber.log.Timber
-import java.io.ByteArrayInputStream
-import java.io.File
-import java.io.IOException
-import java.lang.reflect.InvocationHandler
-import java.lang.reflect.Proxy
-import java.security.SecureRandom
-import java.security.Signature
-
-class CheckSafetyNetEvent(
- private val callback: (SafetyNetResult) -> Unit = {}
-) : ViewEventWithScope(), ContextExecutor, SafetyNetHelper.Callback {
-
- private val svc get() = ServiceLocator.networkService
-
- private lateinit var jar: File
- private lateinit var nonce: ByteArray
-
- override fun invoke(context: Context) {
- jar = File("${context.filesDir.parent}/snet", "snet.jar")
-
- scope.launch(Dispatchers.IO) {
- attest(context) {
- // Download and retry
- Shell.sh("rm -rf " + jar.parent).exec()
- jar.parentFile?.mkdir()
- withContext(Dispatchers.Main) {
- showDialog(context)
- }
- }
- }
- }
-
- private suspend fun attest(context: Context, onError: suspend (Exception) -> Unit) {
- val helper: SafetyNetHelper
- try {
- val loader = createClassLoader(jar)
-
- // Scan through the dex and find our helper class
- var clazz: Class<*>? = null
- loop@for (dex in loader.getDexFiles()) {
- for (name in dex.entries()) {
- val cls = loader.loadClass(name)
- if (InvocationHandler::class.java.isAssignableFrom(cls)) {
- clazz = cls
- break@loop
- }
- }
- }
- clazz ?: throw Exception("Cannot find SafetyNetHelper implementation")
-
- helper = Proxy.newProxyInstance(
- loader, arrayOf(SafetyNetHelper::class.java),
- clazz.newInstance() as InvocationHandler) as SafetyNetHelper
-
- if (helper.version != Const.SNET_EXT_VER)
- throw Exception("snet extension version mismatch")
- } catch (e: Exception) {
- onError(e)
- return
- }
-
- val random = SecureRandom()
- nonce = ByteArray(24)
- random.nextBytes(nonce)
- helper.attest(context, nonce, this)
- }
-
- // All of these fields are whitelisted
- private fun BaseDexClassLoader.getDexFiles(): List {
- val pathList = BaseDexClassLoader::class.java.reflectField("pathList").get(this)
- val dexElements = pathList.javaClass.reflectField("dexElements").get(pathList) as Array<*>
- val fileField = dexElements.javaClass.componentType.reflectField("dexFile")
- return dexElements.map { fileField.get(it) as DexFile }
- }
-
- private fun download(context: Context) = scope.launch(Dispatchers.IO) {
- val abort: suspend (Exception) -> Unit = {
- Timber.e(it)
- withContext(Dispatchers.Main) {
- callback(SafetyNetResult())
- }
- }
- try {
- svc.fetchSafetynet().byteStream().writeTo(jar)
- attest(context, abort)
- } catch (e: IOException) {
- abort(e)
- }
- }
-
- private fun showDialog(context: Context) {
- MagiskDialog(context)
- .applyTitle(R.string.proprietary_title)
- .applyMessage(R.string.proprietary_notice)
- .cancellable(false)
- .applyButton(MagiskDialog.ButtonType.POSITIVE) {
- titleRes = android.R.string.ok
- onClick { download(context) }
- }
- .applyButton(MagiskDialog.ButtonType.NEGATIVE) {
- titleRes = android.R.string.cancel
- onClick { callback(SafetyNetResult(dismiss = true)) }
- }
- .onCancel {
- callback(SafetyNetResult(dismiss = true))
- }
- .reveal()
- }
-
- private fun String.decode(): ByteArray {
- return if (contains("[+/]".toRegex()))
- Base64.decode(this, Base64.DEFAULT)
- else
- Base64.decode(this, Base64.URL_SAFE)
- }
-
- private fun String.parseJws(): SafetyNetResponse {
- val jws = split('.')
- val secondDot = lastIndexOf('.')
- val rawHeader = String(jws[0].decode())
- val payload = String(jws[1].decode())
- var signature = jws[2].decode()
- val signedBytes = substring(0, secondDot).toByteArray()
-
- val moshi = Moshi.Builder().build()
- val header = moshi.adapter(JwsHeader::class.java).fromJson(rawHeader)
- ?: error("Invalid JWS header")
-
- val alg = when (header.algorithm) {
- "RS256" -> "SHA256withRSA"
- "ES256" -> {
- // Convert to DER encoding
- signature = ASN1Primitive.fromByteArray(signature).getEncoded(ASN1Encoding.DER)
- "SHA256withECDSA"
- }
- else -> error("Unsupported algorithm: ${header.algorithm}")
- }
-
- // Verify signature
- val certB64 = header.certificates?.first() ?: error("Cannot find certificate in JWS")
- val bis = ByteArrayInputStream(certB64.decode())
- val cert = CryptoUtils.readCertificate(bis)
- val verifier = Signature.getInstance(alg)
- verifier.initVerify(cert.publicKey)
- verifier.update(signedBytes)
- if (!verifier.verify(signature))
- error("Signature mismatch")
-
- // Verify hostname
- val hostnameVerifier = JsseDefaultHostnameAuthorizer(setOf())
- if (!hostnameVerifier.verify("attest.android.com", cert))
- error("Hostname mismatch")
-
- val response = moshi.adapter(SafetyNetResponse::class.java).fromJson(payload)
- ?: error("Invalid SafetyNet response")
-
- // Verify results
- if (!response.nonce.decode().contentEquals(nonce))
- error("nonce mismatch")
-
- return response
- }
-
- override fun onResponse(response: String?) {
- if (response != null) {
- scope.launch(Dispatchers.Default) {
- val res = runCatching { response.parseJws() }.getOrElse {
- Timber.e(it)
- INVALID_RESPONSE
- }
- withContext(Dispatchers.Main) {
- callback(SafetyNetResult(res))
- }
- }
- } else {
- callback(SafetyNetResult())
- }
- }
-}
-
-@JsonClass(generateAdapter = true)
-data class JwsHeader(
- @Json(name = "alg") val algorithm: String,
- @Json(name = "x5c") val certificates: List?
-)
-
-@JsonClass(generateAdapter = true)
-data class SafetyNetResponse(
- val nonce: String,
- val ctsProfileMatch: Boolean,
- val basicIntegrity: Boolean,
- val evaluationType: String = ""
-)
-
-// Special instance to indicate invalid SafetyNet response
-val INVALID_RESPONSE = SafetyNetResponse("", ctsProfileMatch = false, basicIntegrity = false)
diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/safetynet/SafetyNetHelper.kt b/app/src/main/java/com/topjohnwu/magisk/ui/safetynet/SafetyNetHelper.kt
deleted file mode 100644
index 92ef7c4ec..000000000
--- a/app/src/main/java/com/topjohnwu/magisk/ui/safetynet/SafetyNetHelper.kt
+++ /dev/null
@@ -1,14 +0,0 @@
-package com.topjohnwu.magisk.ui.safetynet
-
-import android.content.Context
-
-interface SafetyNetHelper {
-
- val version: Int
-
- fun attest(context: Context, nonce: ByteArray, callback: Callback)
-
- interface Callback {
- fun onResponse(response: String?)
- }
-}
diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/safetynet/SafetynetFragment.kt b/app/src/main/java/com/topjohnwu/magisk/ui/safetynet/SafetynetFragment.kt
deleted file mode 100644
index 191fda09f..000000000
--- a/app/src/main/java/com/topjohnwu/magisk/ui/safetynet/SafetynetFragment.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-package com.topjohnwu.magisk.ui.safetynet
-
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import com.topjohnwu.magisk.R
-import com.topjohnwu.magisk.arch.BaseUIFragment
-import com.topjohnwu.magisk.databinding.FragmentSafetynetMd2Binding
-import com.topjohnwu.magisk.di.viewModel
-
-class SafetynetFragment : BaseUIFragment() {
-
- override val layoutRes = R.layout.fragment_safetynet_md2
- override val viewModel by viewModel()
-
- override fun onStart() {
- super.onStart()
- activity.setTitle(R.string.safetynet)
- }
-
- override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
- super.onCreateView(inflater, container, savedInstanceState)
-
- // Set barrier reference IDs in code, since resource IDs will be stripped in release mode
- binding.snetBarrier.referencedIds = intArrayOf(R.id.basic_text, R.id.cts_text)
-
- return binding.root
- }
-
-}
diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/safetynet/SafetynetViewModel.kt b/app/src/main/java/com/topjohnwu/magisk/ui/safetynet/SafetynetViewModel.kt
deleted file mode 100644
index 7d0c138b0..000000000
--- a/app/src/main/java/com/topjohnwu/magisk/ui/safetynet/SafetynetViewModel.kt
+++ /dev/null
@@ -1,95 +0,0 @@
-package com.topjohnwu.magisk.ui.safetynet
-
-import androidx.databinding.Bindable
-import com.topjohnwu.magisk.BR
-import com.topjohnwu.magisk.R
-import com.topjohnwu.magisk.arch.BaseViewModel
-import com.topjohnwu.magisk.databinding.set
-
-class SafetyNetResult(
- val response: SafetyNetResponse? = null,
- val dismiss: Boolean = false
-)
-
-class SafetynetViewModel : BaseViewModel() {
-
- @get:Bindable
- var safetyNetTitle = R.string.empty
- set(value) = set(value, field, { field = it }, BR.safetyNetTitle)
-
- @get:Bindable
- var ctsState = false
- set(value) = set(value, field, { field = it }, BR.ctsState)
-
- @get:Bindable
- var basicIntegrityState = false
- set(value) = set(value, field, { field = it }, BR.basicIntegrityState)
-
- @get:Bindable
- var evalType = ""
- set(value) = set(value, field, { field = it }, BR.evalType)
-
- @get:Bindable
- var isChecking = false
- set(value) = set(value, field, { field = it }, BR.checking)
-
- @get:Bindable
- var isSuccess = false
- set(value) = set(value, field, { field = it }, BR.success, BR.textColorAttr)
-
- @get:Bindable
- val textColorAttr get() = if (isSuccess) R.attr.colorOnPrimary else R.attr.colorOnError
-
- init {
- cachedResult?.also {
- handleResult(SafetyNetResult(it))
- } ?: attest()
- }
-
- private fun attest() {
- isChecking = true
- CheckSafetyNetEvent(::handleResult).publish()
- }
-
- fun reset() = attest()
-
- private fun handleResult(result: SafetyNetResult) {
- isChecking = false
-
- if (result.dismiss) {
- back()
- return
- }
-
- result.response?.apply {
- cachedResult = this
- if (this === INVALID_RESPONSE) {
- isSuccess = false
- ctsState = false
- basicIntegrityState = false
- evalType = "N/A"
- safetyNetTitle = R.string.safetynet_res_invalid
- } else {
- val success = ctsProfileMatch && basicIntegrity
- isSuccess = success
- ctsState = ctsProfileMatch
- basicIntegrityState = basicIntegrity
- evalType = if (evaluationType.contains("HARDWARE")) "HARDWARE" else "BASIC"
- safetyNetTitle =
- if (success) R.string.safetynet_attest_success
- else R.string.safetynet_attest_failure
- }
- } ?: run {
- isSuccess = false
- ctsState = false
- basicIntegrityState = false
- evalType = "N/A"
- safetyNetTitle = R.string.safetynet_api_error
- }
- }
-
- companion object {
- private var cachedResult: SafetyNetResponse? = null
- }
-
-}
diff --git a/app/src/main/res/layout/fragment_home_md2.xml b/app/src/main/res/layout/fragment_home_md2.xml
index a1e70eef4..3ca1994a5 100644
--- a/app/src/main/res/layout/fragment_home_md2.xml
+++ b/app/src/main/res/layout/fragment_home_md2.xml
@@ -108,24 +108,10 @@
app:layout_constraintTop_toBottomOf="@+id/home_magisk_wrapper" />
-
-