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
This commit is contained in:
Viktor De Pasquale 2019-10-23 21:17:53 +02:00
parent 04576ca828
commit 21f2f86cb8
6 changed files with 201 additions and 7 deletions

View File

@ -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() }

View File

@ -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

View File

@ -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<MagiskRepository>()
private val rxBus by inject<RxBus>()
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)

View File

@ -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()
class SafetynetViewModel(
rxBus: RxBus
) : CompatViewModel() {
val safetyNetTitle = KObservableField(R.string.empty)
val ctsState = KObservableField(IDLE)
val basicIntegrityState = KObservableField(IDLE)
init {
rxBus.register<SafetyNetResult>()
.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
}
}
}
}

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="?colorOnSurface"
android:pathData="M7,2V4H8V18A4,4 0 0,0 12,22A4,4 0 0,0 16,18V4H17V2H7M11,16C10.4,16 10,15.6 10,15C10,14.4 10.4,14 11,14C11.6,14 12,14.4 12,15C12,15.6 11.6,16 11,16M13,12C12.4,12 12,11.6 12,11C12,10.4 12.4,10 13,10C13.6,10 14,10.4 14,11C14,11.6 13.6,12 13,12M14,7H10V4H14V7Z" />
</vector>

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
@ -9,15 +10,41 @@
</data>
<androidx.core.widget.NestedScrollView
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" />
android:layout_height="match_parent">
</androidx.core.widget.NestedScrollView>
<com.google.android.material.button.MaterialButton
style="?styleButtonText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{() -> viewModel.attest()}"
android:text="Attest"
android:textAllCaps="false"
app:icon="@drawable/ic_test"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?colorPrimary">
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>
</layout>