From dbe0df86ec76fcc78075bd653a52884cba4e3b04 Mon Sep 17 00:00:00 2001
From: Nullptr <52071314+Dr-TSNG@users.noreply.github.com>
Date: Tue, 5 Apr 2022 17:21:31 +0800
Subject: [PATCH] Support custom keystore for manager
---
manager/build.gradle.kts | 14 +-
manager/src/main/AndroidManifest.xml | 3 +-
.../org/lsposed/lspatch/LSPApplication.kt | 12 +
.../main/java/org/lsposed/lspatch/Patcher.kt | 10 +-
.../org/lsposed/lspatch/config/MyKeyStore.kt | 54 +++++
.../org/lsposed/lspatch/ui/page/PageList.kt | 2 +-
.../lsposed/lspatch/ui/page/SettingsPage.kt | 214 ++++++++++++++++++
manager/src/main/res/values/strings.xml | 17 +-
8 files changed, 312 insertions(+), 14 deletions(-)
create mode 100644 manager/src/main/java/org/lsposed/lspatch/config/MyKeyStore.kt
create mode 100644 manager/src/main/java/org/lsposed/lspatch/ui/page/SettingsPage.kt
diff --git a/manager/build.gradle.kts b/manager/build.gradle.kts
index 4435a08..e99756a 100644
--- a/manager/build.gradle.kts
+++ b/manager/build.gradle.kts
@@ -68,19 +68,19 @@ dependencies {
implementation(projects.share.android)
implementation("androidx.core:core-ktx:1.7.0")
- implementation("androidx.activity:activity-compose:1.5.0-alpha03")
+ implementation("androidx.activity:activity-compose:1.6.0-alpha01")
implementation("androidx.compose.material:material-icons-extended:1.1.1")
- implementation("androidx.compose.material3:material3:1.0.0-alpha07")
+ implementation("androidx.compose.material3:material3:1.0.0-alpha08")
implementation("androidx.compose.runtime:runtime-livedata:1.1.1")
implementation("androidx.compose.ui:ui:1.1.1")
implementation("androidx.compose.ui:ui-tooling:1.1.1")
- implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.5.0-alpha04")
+ implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.5.0-alpha05")
implementation("androidx.navigation:navigation-compose:2.5.0-alpha03")
implementation("androidx.preference:preference:1.2.0")
- implementation("com.google.accompanist:accompanist-drawablepainter:0.24.2-alpha")
- implementation("com.google.accompanist:accompanist-navigation-animation:0.24.2-alpha")
- implementation("com.google.accompanist:accompanist-permissions:0.24.2-alpha")
- implementation("com.google.accompanist:accompanist-swiperefresh:0.24.2-alpha")
+ implementation("com.google.accompanist:accompanist-drawablepainter:0.24.5-alpha")
+ implementation("com.google.accompanist:accompanist-navigation-animation:0.24.5-alpha")
+ implementation("com.google.accompanist:accompanist-permissions:0.24.5-alpha")
+ implementation("com.google.accompanist:accompanist-swiperefresh:0.24.5-alpha")
implementation("com.google.android.material:material:1.5.0")
implementation("dev.rikka.shizuku:api:12.1.0")
implementation("dev.rikka.shizuku:provider:12.1.0")
diff --git a/manager/src/main/AndroidManifest.xml b/manager/src/main/AndroidManifest.xml
index c7c1d1f..7953c1f 100644
--- a/manager/src/main/AndroidManifest.xml
+++ b/manager/src/main/AndroidManifest.xml
@@ -12,6 +12,7 @@
android:maxSdkVersion="28" />
diff --git a/manager/src/main/java/org/lsposed/lspatch/LSPApplication.kt b/manager/src/main/java/org/lsposed/lspatch/LSPApplication.kt
index 5bd563b..7103c22 100644
--- a/manager/src/main/java/org/lsposed/lspatch/LSPApplication.kt
+++ b/manager/src/main/java/org/lsposed/lspatch/LSPApplication.kt
@@ -1,6 +1,8 @@
package org.lsposed.lspatch
import android.app.Application
+import android.content.Context
+import android.content.SharedPreferences
import android.content.pm.PackageManager
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -15,6 +17,9 @@ class LSPApplication : Application() {
var shizukuBinderAvalable = false
var shizukuGranted by mutableStateOf(false)
+ lateinit var appContext: Context
+ lateinit var prefs: SharedPreferences
+
init {
Shizuku.addBinderReceivedListenerSticky {
shizukuBinderAvalable = true
@@ -26,4 +31,11 @@ class LSPApplication : Application() {
}
}
}
+
+ override fun onCreate() {
+ super.onCreate()
+ appContext = applicationContext
+ appContext.filesDir.mkdir()
+ prefs = appContext.getSharedPreferences("settings", Context.MODE_PRIVATE)
+ }
}
diff --git a/manager/src/main/java/org/lsposed/lspatch/Patcher.kt b/manager/src/main/java/org/lsposed/lspatch/Patcher.kt
index eaf3897..09eccc1 100644
--- a/manager/src/main/java/org/lsposed/lspatch/Patcher.kt
+++ b/manager/src/main/java/org/lsposed/lspatch/Patcher.kt
@@ -7,6 +7,7 @@ import android.os.Environment
import android.provider.MediaStore
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
+import org.lsposed.lspatch.config.MyKeyStore
import org.lsposed.patch.LSPatch
import org.lsposed.patch.util.Logger
import java.io.File
@@ -29,7 +30,7 @@ object Patcher {
lateinit var outputPath: String
fun toStringArray(): Array {
- return arrayListOf().run {
+ return buildList {
addAll(apkPaths)
add("-o"); add(outputPath)
if (debuggable) add("-d")
@@ -43,9 +44,10 @@ object Patcher {
if (embeddedModules.isNotEmpty()) {
add("-m"); addAll(embeddedModules)
}
-
- toTypedArray()
- }
+ if (!MyKeyStore.useDefault) {
+ addAll(arrayOf("-k", MyKeyStore.file.path, MyKeyStore.password, MyKeyStore.alias,MyKeyStore.aliasPassword))
+ }
+ }.toTypedArray()
}
}
diff --git a/manager/src/main/java/org/lsposed/lspatch/config/MyKeyStore.kt b/manager/src/main/java/org/lsposed/lspatch/config/MyKeyStore.kt
new file mode 100644
index 0000000..7a36943
--- /dev/null
+++ b/manager/src/main/java/org/lsposed/lspatch/config/MyKeyStore.kt
@@ -0,0 +1,54 @@
+package org.lsposed.lspatch.config
+
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+import org.lsposed.lspatch.LSPApplication.Companion.appContext
+import org.lsposed.lspatch.LSPApplication.Companion.prefs
+import java.io.File
+
+object MyKeyStore {
+
+ val file = File("${appContext.filesDir}/keystore.bks")
+
+ val tmpFile = File("${appContext.filesDir}/keystore.bks.tmp")
+
+ val password: String
+ get() = prefs.getString("keystore_password", "123456")!!
+
+ val alias: String
+ get() = prefs.getString("keystore_alias", "key0")!!
+
+ val aliasPassword: String
+ get() = prefs.getString("keystore_alias_password", "123456")!!
+
+ private var mUseDefault by mutableStateOf(!file.exists())
+ val useDefault by derivedStateOf { mUseDefault }
+
+ suspend fun reset() {
+ withContext(Dispatchers.IO) {
+ file.delete()
+ prefs.edit()
+ .putString("keystore_password", "123456")
+ .putString("keystore_alias", "key0")
+ .putString("keystore_alias_password", "123456")
+ .apply()
+ mUseDefault = true
+ }
+ }
+
+ suspend fun setCustom(password: String, alias: String, aliasPassword: String) {
+ withContext(Dispatchers.IO) {
+ tmpFile.renameTo(file)
+ prefs.edit()
+ .putString("keystore_password", password)
+ .putString("keystore_alias", alias)
+ .putString("keystore_alias_password", aliasPassword)
+ .apply()
+ mUseDefault = false
+ }
+ }
+}
diff --git a/manager/src/main/java/org/lsposed/lspatch/ui/page/PageList.kt b/manager/src/main/java/org/lsposed/lspatch/ui/page/PageList.kt
index 34928a9..2c1bb01 100644
--- a/manager/src/main/java/org/lsposed/lspatch/ui/page/PageList.kt
+++ b/manager/src/main/java/org/lsposed/lspatch/ui/page/PageList.kt
@@ -41,7 +41,7 @@ enum class PageList(
Settings(
iconSelected = Icons.Filled.Settings,
iconNotSelected = Icons.Outlined.Settings,
- body = {}
+ body = { SettingsPage() }
),
NewPatch(
body = { NewPatchPage(this) }
diff --git a/manager/src/main/java/org/lsposed/lspatch/ui/page/SettingsPage.kt b/manager/src/main/java/org/lsposed/lspatch/ui/page/SettingsPage.kt
new file mode 100644
index 0000000..8f0d417
--- /dev/null
+++ b/manager/src/main/java/org/lsposed/lspatch/ui/page/SettingsPage.kt
@@ -0,0 +1,214 @@
+package org.lsposed.lspatch.ui.page
+
+import androidx.activity.compose.rememberLauncherForActivityResult
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.interaction.PressInteraction
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.Ballot
+import androidx.compose.material3.*
+import androidx.compose.runtime.*
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import kotlinx.coroutines.launch
+import org.lsposed.lspatch.R
+import org.lsposed.lspatch.config.MyKeyStore
+import org.lsposed.lspatch.ui.component.settings.SettingsItem
+import java.io.IOException
+import java.security.GeneralSecurityException
+import java.security.KeyStore
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun SettingsPage() {
+ Scaffold(
+ topBar = { TopBar() }
+ ) { innerPadding ->
+ Column(
+ modifier = Modifier
+ .padding(innerPadding)
+ .verticalScroll(rememberScrollState())
+ ) {
+ KeyStore()
+ }
+ }
+}
+
+@Composable
+private fun TopBar() {
+ SmallTopAppBar(
+ title = { Text(stringResource(R.string.page_settings)) }
+ )
+}
+
+@Composable
+private fun KeyStore() {
+ val context = LocalContext.current
+ val scope = rememberCoroutineScope()
+ var dropDownExpanded by remember { mutableStateOf(false) }
+ var showDialog by remember { mutableStateOf(false) }
+
+ Box {
+ SettingsItem(
+ onClick = { dropDownExpanded = !dropDownExpanded },
+ icon = Icons.Outlined.Ballot,
+ title = stringResource(R.string.settings_keystore),
+ desc = stringResource(if (MyKeyStore.useDefault) R.string.settings_keystore_default else R.string.settings_keystore_custom)
+ )
+ DropdownMenu(expanded = dropDownExpanded, onDismissRequest = { dropDownExpanded = false }) {
+ DropdownMenuItem(
+ text = { Text(stringResource(R.string.settings_keystore_default)) },
+ onClick = {
+ scope.launch { MyKeyStore.reset() }
+ dropDownExpanded = false
+ }
+ )
+ DropdownMenuItem(
+ text = { Text(stringResource(R.string.settings_keystore_custom)) },
+ onClick = { showDialog = true }
+ )
+ }
+ }
+
+ if (showDialog) {
+ var wrongKeystore by rememberSaveable { mutableStateOf(false) }
+ var wrongPassword by rememberSaveable { mutableStateOf(false) }
+ var wrongAliasName by rememberSaveable { mutableStateOf(false) }
+ var wrongAliasPassword by rememberSaveable { mutableStateOf(false) }
+
+ var path by rememberSaveable { mutableStateOf("") }
+ var password by rememberSaveable { mutableStateOf("") }
+ var alias by rememberSaveable { mutableStateOf("") }
+ var aliasPassword by rememberSaveable { mutableStateOf("") }
+
+ val launcher = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri ->
+ if (uri == null) return@rememberLauncherForActivityResult
+ context.contentResolver.openInputStream(uri).use { input ->
+ MyKeyStore.tmpFile.outputStream().use { output ->
+ input?.copyTo(output)
+ }
+ }
+ path = uri.path ?: ""
+ }
+
+ AlertDialog(
+ onDismissRequest = { dropDownExpanded = false; showDialog = false },
+ confirmButton = {
+ TextButton(
+ content = { Text(stringResource(android.R.string.ok)) },
+ onClick = {
+ wrongKeystore = false
+ wrongPassword = false
+ wrongAliasName = false
+ wrongAliasPassword = false
+
+ if (path.isEmpty()) {
+ wrongKeystore = true
+ return@TextButton
+ }
+ val keyStore = KeyStore.getInstance(KeyStore.getDefaultType())
+ try {
+ MyKeyStore.tmpFile.inputStream().use { input ->
+ keyStore.load(input, password.toCharArray())
+ }
+ } catch (e: IOException) {
+ wrongKeystore = true
+ if (e.message == "KeyStore integrity check failed.") {
+ wrongPassword = true
+ }
+ return@TextButton
+ }
+ if (!keyStore.containsAlias(alias)) {
+ wrongAliasName = true
+ return@TextButton
+ }
+ try {
+ keyStore.getKey(alias, aliasPassword.toCharArray())
+ } catch (e: GeneralSecurityException) {
+ wrongAliasPassword = true
+ return@TextButton
+ }
+
+ scope.launch { MyKeyStore.setCustom(password, alias, aliasPassword) }
+ dropDownExpanded = false
+ showDialog = false
+ })
+ },
+ dismissButton = {
+ TextButton(
+ content = { Text(stringResource(android.R.string.cancel)) },
+ onClick = { dropDownExpanded = false; showDialog = false }
+ )
+ },
+ title = { Text(stringResource(R.string.settings_keystore_dialog_title)) },
+ text = {
+ Column {
+ val interactionSource = remember { MutableInteractionSource() }
+ LaunchedEffect(interactionSource) {
+ interactionSource.interactions.collect { interaction ->
+ if (interaction is PressInteraction.Release) {
+ launcher.launch("*/*")
+ }
+ }
+ }
+
+ val wrongText = when {
+ wrongAliasPassword -> stringResource(R.string.settings_keystore_wrong_keystore)
+ wrongAliasName -> stringResource(R.string.settings_keystore_wrong_alias)
+ wrongPassword -> stringResource(R.string.settings_keystore_wrong_password)
+ wrongKeystore -> stringResource(R.string.settings_keystore_wrong_keystore)
+ else -> null
+ }
+ Text(
+ modifier = Modifier
+ .align(Alignment.CenterHorizontally)
+ .padding(bottom = 8.dp),
+ text = wrongText ?: stringResource(R.string.settings_keystore_desc),
+ color = if (wrongText != null) MaterialTheme.colorScheme.error else Color.Unspecified
+ )
+
+ OutlinedTextField(
+ value = path,
+ onValueChange = { path = it },
+ readOnly = true,
+ label = { Text(stringResource(R.string.settings_keystore_file)) },
+ singleLine = true,
+ isError = wrongKeystore,
+ interactionSource = interactionSource
+ )
+ OutlinedTextField(
+ value = password,
+ onValueChange = { password = it },
+ label = { Text(stringResource(R.string.settings_keystore_password)) },
+ singleLine = true,
+ isError = wrongPassword
+ )
+ OutlinedTextField(
+ value = alias,
+ onValueChange = { alias = it },
+ label = { Text(stringResource(R.string.settings_keystore_alias)) },
+ singleLine = true,
+ isError = wrongAliasName
+ )
+ OutlinedTextField(
+ value = aliasPassword,
+ onValueChange = { aliasPassword = it },
+ label = { Text(stringResource(R.string.settings_keystore_alias_password)) },
+ singleLine = true,
+ isError = wrongAliasPassword
+ )
+ }
+ }
+ )
+ }
+}
diff --git a/manager/src/main/res/values/strings.xml b/manager/src/main/res/values/strings.xml
index ebee0dc..43872c3 100644
--- a/manager/src/main/res/values/strings.xml
+++ b/manager/src/main/res/values/strings.xml
@@ -3,7 +3,6 @@
Repo
Logs
- Settings
Shizuku service available
@@ -48,4 +47,20 @@
Select Apps
+
+
+ Settings
+ Signature keystore
+ Built-in
+ Custom
+ Custom keystore
+ Keystore file
+ Password
+ Alias
+ Alias password
+ Set keystore (BKS)
+ Wrong type of keystore
+ Wrong keystore password
+ Wrong alias name
+ Wrong alias password