diff --git a/app/src/main/java/com/topjohnwu/magisk/core/Const.kt b/app/src/main/java/com/topjohnwu/magisk/core/Const.kt index d643fc4fb..3585f738e 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/Const.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/Const.kt @@ -12,8 +12,8 @@ object Const { const val MAGISK_LOG = "/cache/magisk.log" // Versions - const val SNET_EXT_VER = 13 - const val SNET_REVISION = "a6c47f86f10b310358afa9dbe837037dd5d561df" + const val SNET_EXT_VER = 14 + const val SNET_REVISION = "5e28617412bdad2396eab87fa786094d8242e568" const val BOOTCTL_REVISION = "a6c47f86f10b310358afa9dbe837037dd5d561df" // Misc diff --git a/app/src/main/java/com/topjohnwu/magisk/core/utils/SafetyNetHelper.kt b/app/src/main/java/com/topjohnwu/magisk/core/utils/SafetyNetHelper.kt index c27fc1836..59e2a014d 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/utils/SafetyNetHelper.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/utils/SafetyNetHelper.kt @@ -1,5 +1,7 @@ package com.topjohnwu.magisk.core.utils +import org.json.JSONObject + interface SafetyNetHelper { val version: Int @@ -7,15 +9,6 @@ interface SafetyNetHelper { fun attest() interface Callback { - fun onResponse(responseCode: Int) - } - - companion object { - - const val RESPONSE_ERR = 0x01 - const val CONNECTION_FAIL = 0x02 - - const val BASIC_PASS = 0x10 - const val CTS_PASS = 0x20 + fun onResponse(response: JSONObject?) } } diff --git a/app/src/main/java/com/topjohnwu/magisk/model/events/RxEvents.kt b/app/src/main/java/com/topjohnwu/magisk/model/events/RxEvents.kt index fd9081a90..e4fbd8e01 100644 --- a/app/src/main/java/com/topjohnwu/magisk/model/events/RxEvents.kt +++ b/app/src/main/java/com/topjohnwu/magisk/model/events/RxEvents.kt @@ -2,10 +2,14 @@ package com.topjohnwu.magisk.model.events import com.topjohnwu.magisk.core.model.MagiskPolicy import com.topjohnwu.magisk.utils.RxBus +import org.json.JSONObject sealed class PolicyUpdateEvent(val item: MagiskPolicy) : RxBus.Event { class Notification(item: MagiskPolicy) : PolicyUpdateEvent(item) class Log(item: MagiskPolicy) : PolicyUpdateEvent(item) } -data class SafetyNetResult(val responseCode: Int) : RxBus.Event +data class SafetyNetResult( + val response: JSONObject? = null, + val dismiss: Boolean = false +) : RxBus.Event diff --git a/app/src/main/java/com/topjohnwu/magisk/model/events/ViewEvents.kt b/app/src/main/java/com/topjohnwu/magisk/model/events/ViewEvents.kt index 267ff209a..1370fe462 100644 --- a/app/src/main/java/com/topjohnwu/magisk/model/events/ViewEvents.kt +++ b/app/src/main/java/com/topjohnwu/magisk/model/events/ViewEvents.kt @@ -10,6 +10,7 @@ import com.topjohnwu.magisk.core.model.module.Repo import com.topjohnwu.magisk.core.utils.SafetyNetHelper import com.topjohnwu.magisk.data.repository.MagiskRepository import com.topjohnwu.magisk.extensions.DynamicClassLoader +import com.topjohnwu.magisk.extensions.OnErrorListener import com.topjohnwu.magisk.extensions.subscribeK import com.topjohnwu.magisk.extensions.writeTo import com.topjohnwu.magisk.utils.RxBus @@ -19,8 +20,10 @@ import com.topjohnwu.superuser.Shell import dalvik.system.DexFile import io.reactivex.Completable import io.reactivex.subjects.PublishSubject +import org.json.JSONObject import org.koin.core.KoinComponent import org.koin.core.inject +import timber.log.Timber import java.io.File import java.lang.reflect.InvocationHandler @@ -40,18 +43,25 @@ class UpdateSafetyNetEvent : ViewEvent(), ContextExecutor, KoinComponent, Safety private val magiskRepo by inject() private val rxBus by inject() - private lateinit var EXT_APK: File - private lateinit var EXT_DEX: File + private lateinit var apk: File + private lateinit var dex: File override fun invoke(context: Context) { - val die = ::EXT_APK.isInitialized + apk = File("${context.filesDir.parent}/snet", "snet.jar") + dex = File(apk.parent, "snet.dex") - EXT_APK = File("${context.filesDir.parent}/snet", "snet.jar") - EXT_DEX = File(EXT_APK.parent, "snet.dex") + attest(context) { + // Download and retry + Shell.sh("rm -rf " + apk.parent).exec() + apk.parentFile?.mkdir() + download(context, true) + } + } + private fun attest(context: Context, onError: OnErrorListener) { Completable.fromAction { - val loader = DynamicClassLoader(EXT_APK) - val dex = DexFile.loadDex(EXT_APK.path, EXT_DEX.path, 0) + val loader = DynamicClassLoader(apk) + val dex = DexFile.loadDex(apk.path, dex.path, 0) // Scan through the dex and find our helper class var helperClass: Class<*>? = null @@ -66,32 +76,25 @@ class UpdateSafetyNetEvent : ViewEvent(), ContextExecutor, KoinComponent, Safety } helperClass ?: throw Exception() - val helper = helperClass.getMethod( - "get", - Class::class.java, Context::class.java, Any::class.java - ) + val helper = helperClass + .getMethod("get", Class::class.java, Context::class.java, Any::class.java) .invoke(null, SafetyNetHelper::class.java, context, this) as SafetyNetHelper if (helper.version < Const.SNET_EXT_VER) throw Exception() helper.attest() - }.subscribeK(onError = { - if (die) { - rxBus.post(SafetyNetResult(-1)) - } else { - Shell.sh("rm -rf " + EXT_APK.parent).exec() - EXT_APK.parentFile?.mkdir() - download(context, true) - } - }) + }.subscribeK(onError = onError) } @Suppress("SameParameterValue") private fun download(context: Context, askUser: Boolean) { fun downloadInternal() = magiskRepo.fetchSafetynet() - .map { it.byteStream().writeTo(EXT_APK) } - .subscribeK { invoke(context) } + .map { it.byteStream().writeTo(apk) } + .subscribeK { attest(context) { + Timber.e(it) + rxBus.post(SafetyNetResult()) + } } if (!askUser) { downloadInternal() @@ -107,14 +110,14 @@ class UpdateSafetyNetEvent : ViewEvent(), ContextExecutor, KoinComponent, Safety onClick { downloadInternal() } } .applyButton(MagiskDialog.ButtonType.NEGATIVE) { - titleRes = android.R.string.no - onClick { rxBus.post(SafetyNetResult(-2)) } + titleRes = android.R.string.cancel + onClick { rxBus.post(SafetyNetResult(dismiss = true)) } } .reveal() } - override fun onResponse(responseCode: Int) { - rxBus.post(SafetyNetResult(responseCode)) + override fun onResponse(response: JSONObject?) { + rxBus.post(SafetyNetResult(response)) } } 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 index 00b49cfa6..6916f28aa 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/safetynet/SafetynetViewModel.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/safetynet/SafetynetViewModel.kt @@ -3,7 +3,6 @@ package com.topjohnwu.magisk.ui.safetynet import androidx.databinding.Bindable import com.topjohnwu.magisk.BR import com.topjohnwu.magisk.R -import com.topjohnwu.magisk.core.utils.SafetyNetHelper import com.topjohnwu.magisk.extensions.subscribeK import com.topjohnwu.magisk.model.events.SafetyNetResult import com.topjohnwu.magisk.model.events.UpdateSafetyNetEvent @@ -11,6 +10,7 @@ import com.topjohnwu.magisk.ui.base.BaseViewModel import com.topjohnwu.magisk.ui.safetynet.SafetyNetState.* import com.topjohnwu.magisk.utils.KObservableField import com.topjohnwu.magisk.utils.RxBus +import org.json.JSONObject enum class SafetyNetState { LOADING, PASS, FAILED, IDLE @@ -35,14 +35,12 @@ class SafetynetViewModel( init { rxBus.register() - .subscribeK { resolveResponse(it.responseCode) } + .subscribeK { resolveResponse(it) } .add() - if (safetyNetResult >= 0) { - resolveResponse(safetyNetResult) - } else { - attest() - } + cachedResult?.also { + resolveResponse(SafetyNetResult(it)) + } ?: attest() } override fun notifyStateChanged() { @@ -59,38 +57,40 @@ class SafetynetViewModel( fun reset() = attest() - private fun resolveResponse(response: Int) = when { - response and 0x0F == 0 -> { - val hasCtsPassed = response and SafetyNetHelper.CTS_PASS != 0 - val hasBasicIntegrityPassed = response and SafetyNetHelper.BASIC_PASS != 0 - val result = hasCtsPassed && hasBasicIntegrityPassed - safetyNetResult = response - ctsState.value = hasCtsPassed - basicIntegrityState.value = hasBasicIntegrityPassed - currentState = if (result) PASS else FAILED - safetyNetTitle.value = - if (result) R.string.safetynet_attest_success - else R.string.safetynet_attest_failure - } - response == -2 -> { - currentState = FAILED - ctsState.value = false - basicIntegrityState.value = false + private fun resolveResponse(response: SafetyNetResult) { + if (response.dismiss) { back() + return } - else -> { + + response.response?.apply { + runCatching { + val cts = getBoolean("ctsProfileMatch") + val basic = getBoolean("basicIntegrity") + val result = cts && basic + cachedResult = this + ctsState.value = cts + basicIntegrityState.value = basic + currentState = if (result) PASS else FAILED + safetyNetTitle.value = + if (result) R.string.safetynet_attest_success + else R.string.safetynet_attest_failure + }.onFailure { + currentState = FAILED + ctsState.value = false + basicIntegrityState.value = false + safetyNetTitle.value = R.string.safetynet_res_invalid + } + } ?: { currentState = FAILED ctsState.value = false basicIntegrityState.value = false - safetyNetTitle.value = when (response) { - SafetyNetHelper.RESPONSE_ERR -> R.string.safetynet_res_invalid - else -> R.string.safetynet_api_error - } - } + safetyNetTitle.value = R.string.safetynet_api_error + }() } companion object { - private var safetyNetResult = -1 + private var cachedResult: JSONObject? = null } } diff --git a/build.py b/build.py index 68f6acb1b..56e6710c2 100755 --- a/build.py +++ b/build.py @@ -398,6 +398,22 @@ def build_stub(args): header('* Building Magisk Manager stub') build_apk(args, 'stub') +# Bind mount snet package on top of the stub folder +def build_snet(args): + header('* Building snet extension') + proc = execv([gradlew, 'stub:assembleRelease']) + if proc.returncode != 0: + error('Build snet extention failed!') + source = op.join('stub', 'build', 'outputs', 'apk', + 'release', 'stub-release.apk') + target = op.join(config['outdir'], 'snet.jar') + # Extract classes.dex + with zipfile.ZipFile(target, 'w', compression=zipfile.ZIP_DEFLATED, allowZip64=False) as zout: + with zipfile.ZipFile(source) as zin: + zout.writestr('classes.dex', zin.read('classes.dex')) + rm(source) + header('Output: ' + target) + def zip_main(args): header('* Packing Flashable Zip')