From 21f2f86cb8161bf70cf5cbc6abffbaf65d1c2a95 Mon Sep 17 00:00:00 2001 From: Viktor De Pasquale Date: Wed, 23 Oct 2019 21:17:53 +0200 Subject: [PATCH] Added safetynet implementation The implementation itself was moved from fragment to self contained event. The result resolution might be moved to the event as well --- .../com/topjohnwu/magisk/di/RedesignModule.kt | 2 +- .../topjohnwu/magisk/model/events/RxEvents.kt | 2 + .../magisk/model/events/ViewEvents.kt | 100 +++++++++++++++++- .../redesign/safetynet/SafetynetViewModel.kt | 59 ++++++++++- app/src/main/res/drawable/ic_test.xml | 10 ++ .../res/layout/fragment_safetynet_md2.xml | 35 +++++- 6 files changed, 201 insertions(+), 7 deletions(-) create mode 100644 app/src/main/res/drawable/ic_test.xml diff --git a/app/src/main/java/com/topjohnwu/magisk/di/RedesignModule.kt b/app/src/main/java/com/topjohnwu/magisk/di/RedesignModule.kt index d7367373b..4edd025e0 100644 --- a/app/src/main/java/com/topjohnwu/magisk/di/RedesignModule.kt +++ b/app/src/main/java/com/topjohnwu/magisk/di/RedesignModule.kt @@ -22,7 +22,7 @@ val redesignModule = module { viewModel { LogViewModel() } viewModel { ModuleViewModel() } viewModel { RequestViewModel() } - viewModel { SafetynetViewModel() } + viewModel { SafetynetViewModel(get()) } viewModel { SettingsViewModel() } viewModel { SuperuserViewModel(get(), get(), get(), get()) } viewModel { ThemeViewModel() } 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 a0dd317e4..c3171db84 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 @@ -15,3 +15,5 @@ sealed class PolicyUpdateEvent(val item: MagiskPolicy) : RxBus.Event { } class ModuleUpdatedEvent(val item: ModuleRvItem) : RxBus.Event + +data class SafetyNetResult(val responseCode: Int) : RxBus.Event \ No newline at end of file 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 ce8d439ce..ef5eeb494 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 @@ -1,6 +1,7 @@ package com.topjohnwu.magisk.model.events import android.app.Activity +import android.content.Context import android.content.Intent import androidx.appcompat.app.AppCompatActivity import com.karumi.dexter.Dexter @@ -8,9 +9,25 @@ import com.karumi.dexter.MultiplePermissionsReport import com.karumi.dexter.PermissionToken import com.karumi.dexter.listener.PermissionRequest import com.karumi.dexter.listener.multi.MultiplePermissionsListener +import com.topjohnwu.magisk.Const +import com.topjohnwu.magisk.R +import com.topjohnwu.magisk.data.repository.MagiskRepository +import com.topjohnwu.magisk.extensions.DynamicClassLoader +import com.topjohnwu.magisk.extensions.subscribeK +import com.topjohnwu.magisk.extensions.writeTo import com.topjohnwu.magisk.model.entity.module.Repo import com.topjohnwu.magisk.model.permissions.PermissionRequestBuilder +import com.topjohnwu.magisk.utils.RxBus +import com.topjohnwu.magisk.utils.SafetyNetHelper +import com.topjohnwu.magisk.view.MagiskDialog +import com.topjohnwu.superuser.Shell +import dalvik.system.DexFile +import io.reactivex.Completable import io.reactivex.subjects.PublishSubject +import org.koin.core.KoinComponent +import org.koin.core.inject +import java.io.File +import java.lang.reflect.InvocationHandler /** * Class for passing events from ViewModels to Activities/Fragments @@ -34,7 +51,88 @@ class MagiskChangelogEvent : ViewEvent() class UninstallEvent : ViewEvent() class EnvFixEvent : ViewEvent() -class UpdateSafetyNetEvent : ViewEvent() +class UpdateSafetyNetEvent : ViewEvent(), ContextExecutor, KoinComponent, SafetyNetHelper.Callback { + + private val magiskRepo by inject() + private val rxBus by inject() + + private lateinit var EXT_APK: File + private lateinit var EXT_DEX: File + + override fun invoke(context: Context) { + val die = ::EXT_APK.isInitialized + + EXT_APK = File("${context.filesDir.parent}/snet", "snet.jar") + EXT_DEX = File(EXT_APK.parent, "snet.dex") + + Completable.fromAction { + val loader = DynamicClassLoader(EXT_APK) + val dex = DexFile.loadDex(EXT_APK.path, EXT_DEX.path, 0) + + // Scan through the dex and find our helper class + var helperClass: Class<*>? = null + for (className in dex.entries()) { + if (className.startsWith("x.")) { + val cls = loader.loadClass(className) + if (InvocationHandler::class.java.isAssignableFrom(cls)) { + helperClass = cls + break + } + } + } + helperClass ?: throw Exception() + + 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) + } + }) + } + + @Suppress("SameParameterValue") + private fun download(context: Context, askUser: Boolean) { + fun downloadInternal() = magiskRepo.fetchSafetynet() + .map { it.byteStream().writeTo(EXT_APK) } + .subscribeK { invoke(context) } + + if (!askUser) { + downloadInternal() + return + } + + MagiskDialog(context) + .applyTitle(R.string.proprietary_title) + .applyMessage(R.string.proprietary_notice) + .cancellable(false) + .applyButton(MagiskDialog.ButtonType.POSITIVE) { + titleRes = R.string.yes + onClick { downloadInternal() } + } + .applyButton(MagiskDialog.ButtonType.NEGATIVE) { + titleRes = R.string.no_thanks + onClick { rxBus.post(SafetyNetResult(-2)) } + } + .reveal() + } + + override fun onResponse(responseCode: Int) { + rxBus.post(SafetyNetResult(responseCode)) + } +} class ViewActionEvent(val action: Activity.() -> Unit) : ViewEvent(), ActivityExecutor { override fun invoke(activity: AppCompatActivity) = activity.run(action) diff --git a/app/src/main/java/com/topjohnwu/magisk/redesign/safetynet/SafetynetViewModel.kt b/app/src/main/java/com/topjohnwu/magisk/redesign/safetynet/SafetynetViewModel.kt index 637f3cff9..bdaf1e7d9 100644 --- a/app/src/main/java/com/topjohnwu/magisk/redesign/safetynet/SafetynetViewModel.kt +++ b/app/src/main/java/com/topjohnwu/magisk/redesign/safetynet/SafetynetViewModel.kt @@ -1,5 +1,62 @@ package com.topjohnwu.magisk.redesign.safetynet +import com.topjohnwu.magisk.R +import com.topjohnwu.magisk.extensions.subscribeK +import com.topjohnwu.magisk.model.events.SafetyNetResult +import com.topjohnwu.magisk.model.events.UpdateSafetyNetEvent import com.topjohnwu.magisk.redesign.compat.CompatViewModel +import com.topjohnwu.magisk.ui.home.SafetyNetState.* +import com.topjohnwu.magisk.utils.KObservableField +import com.topjohnwu.magisk.utils.RxBus +import com.topjohnwu.magisk.utils.SafetyNetHelper -class SafetynetViewModel : CompatViewModel() \ No newline at end of file +class SafetynetViewModel( + rxBus: RxBus +) : CompatViewModel() { + + val safetyNetTitle = KObservableField(R.string.empty) + val ctsState = KObservableField(IDLE) + val basicIntegrityState = KObservableField(IDLE) + + init { + rxBus.register() + .subscribeK { resolveResponse(it.responseCode) } + .add() + } + + fun attest() = UpdateSafetyNetEvent().publish() + + private fun resolveResponse(response: Int) = when { + //todo animate (reveal) to result (green/error) + response and 0x0F == 0 -> { + val hasCtsPassed = response and SafetyNetHelper.CTS_PASS != 0 + val hasBasicIntegrityPassed = response and SafetyNetHelper.BASIC_PASS != 0 + safetyNetTitle.value = R.string.safetyNet_check_success + ctsState.value = if (hasCtsPassed) { + PASS + } else { + FAILED + } + basicIntegrityState.value = if (hasBasicIntegrityPassed) { + PASS + } else { + FAILED + } + } + //todo animate (collapse) back to initial (fade error) + response == -2 -> { + ctsState.value = IDLE + basicIntegrityState.value = IDLE + } + //todo animate (collapse) back to initial (surface) + else -> { + ctsState.value = IDLE + basicIntegrityState.value = IDLE + safetyNetTitle.value = when (response) { + SafetyNetHelper.RESPONSE_ERR -> R.string.safetyNet_res_invalid + else -> R.string.safetyNet_api_error + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_test.xml b/app/src/main/res/drawable/ic_test.xml new file mode 100644 index 000000000..f0d3a427b --- /dev/null +++ b/app/src/main/res/drawable/ic_test.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_safetynet_md2.xml b/app/src/main/res/layout/fragment_safetynet_md2.xml index 6afafba71..f2ff97488 100644 --- a/app/src/main/res/layout/fragment_safetynet_md2.xml +++ b/app/src/main/res/layout/fragment_safetynet_md2.xml @@ -1,5 +1,6 @@ - + @@ -9,15 +10,41 @@ - + android:layout_height="match_parent"> - + + + + + + + + + + + \ No newline at end of file