Support custom keystore for manager
This commit is contained in:
parent
02d6752895
commit
dbe0df86ec
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
android:maxSdkVersion="28" />
|
||||
|
||||
<application
|
||||
android:name=".LSPApplication"
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
|
|
@ -38,9 +39,9 @@
|
|||
<provider
|
||||
android:name="rikka.shizuku.ShizukuProvider"
|
||||
android:authorities="org.lsposed.lspatch.shizuku"
|
||||
android:multiprocess="false"
|
||||
android:enabled="true"
|
||||
android:exported="true"
|
||||
android:multiprocess="false"
|
||||
android:permission="android.permission.INTERACT_ACROSS_USERS_FULL" />
|
||||
</application>
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<String> {
|
||||
return arrayListOf<String>().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()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -41,7 +41,7 @@ enum class PageList(
|
|||
Settings(
|
||||
iconSelected = Icons.Filled.Settings,
|
||||
iconNotSelected = Icons.Outlined.Settings,
|
||||
body = {}
|
||||
body = { SettingsPage() }
|
||||
),
|
||||
NewPatch(
|
||||
body = { NewPatchPage(this) }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
<string name="page_repo">Repo</string>
|
||||
<string name="page_logs">Logs</string>
|
||||
<string name="page_settings">Settings</string>
|
||||
|
||||
<!-- Home Page -->
|
||||
<string name="home_shizuku_available">Shizuku service available</string>
|
||||
|
|
@ -48,4 +47,20 @@
|
|||
|
||||
<!-- Select Apps Page -->
|
||||
<string name="page_select_apps">Select Apps</string>
|
||||
|
||||
<!-- Settings Page -->
|
||||
<string name="page_settings">Settings</string>
|
||||
<string name="settings_keystore">Signature keystore</string>
|
||||
<string name="settings_keystore_default">Built-in</string>
|
||||
<string name="settings_keystore_custom">Custom</string>
|
||||
<string name="settings_keystore_dialog_title">Custom keystore</string>
|
||||
<string name="settings_keystore_file">Keystore file</string>
|
||||
<string name="settings_keystore_password">Password</string>
|
||||
<string name="settings_keystore_alias">Alias</string>
|
||||
<string name="settings_keystore_alias_password">Alias password</string>
|
||||
<string name="settings_keystore_desc">Set keystore (BKS)</string>
|
||||
<string name="settings_keystore_wrong_keystore">Wrong type of keystore</string>
|
||||
<string name="settings_keystore_wrong_password">Wrong keystore password</string>
|
||||
<string name="settings_keystore_wrong_alias">Wrong alias name</string>
|
||||
<string name="settings_keystore_wrong_alias_password">Wrong alias password</string>
|
||||
</resources>
|
||||
|
|
|
|||
Loading…
Reference in New Issue