Support custom keystore for manager
This commit is contained in:
parent
02d6752895
commit
dbe0df86ec
|
|
@ -68,19 +68,19 @@ dependencies {
|
||||||
implementation(projects.share.android)
|
implementation(projects.share.android)
|
||||||
|
|
||||||
implementation("androidx.core:core-ktx:1.7.0")
|
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.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.runtime:runtime-livedata:1.1.1")
|
||||||
implementation("androidx.compose.ui:ui:1.1.1")
|
implementation("androidx.compose.ui:ui:1.1.1")
|
||||||
implementation("androidx.compose.ui:ui-tooling: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.navigation:navigation-compose:2.5.0-alpha03")
|
||||||
implementation("androidx.preference:preference:1.2.0")
|
implementation("androidx.preference:preference:1.2.0")
|
||||||
implementation("com.google.accompanist:accompanist-drawablepainter:0.24.2-alpha")
|
implementation("com.google.accompanist:accompanist-drawablepainter:0.24.5-alpha")
|
||||||
implementation("com.google.accompanist:accompanist-navigation-animation:0.24.2-alpha")
|
implementation("com.google.accompanist:accompanist-navigation-animation:0.24.5-alpha")
|
||||||
implementation("com.google.accompanist:accompanist-permissions:0.24.2-alpha")
|
implementation("com.google.accompanist:accompanist-permissions:0.24.5-alpha")
|
||||||
implementation("com.google.accompanist:accompanist-swiperefresh:0.24.2-alpha")
|
implementation("com.google.accompanist:accompanist-swiperefresh:0.24.5-alpha")
|
||||||
implementation("com.google.android.material:material:1.5.0")
|
implementation("com.google.android.material:material:1.5.0")
|
||||||
implementation("dev.rikka.shizuku:api:12.1.0")
|
implementation("dev.rikka.shizuku:api:12.1.0")
|
||||||
implementation("dev.rikka.shizuku:provider:12.1.0")
|
implementation("dev.rikka.shizuku:provider:12.1.0")
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
android:maxSdkVersion="28" />
|
android:maxSdkVersion="28" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
|
android:name=".LSPApplication"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
|
|
@ -38,9 +39,9 @@
|
||||||
<provider
|
<provider
|
||||||
android:name="rikka.shizuku.ShizukuProvider"
|
android:name="rikka.shizuku.ShizukuProvider"
|
||||||
android:authorities="org.lsposed.lspatch.shizuku"
|
android:authorities="org.lsposed.lspatch.shizuku"
|
||||||
android:multiprocess="false"
|
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
|
android:multiprocess="false"
|
||||||
android:permission="android.permission.INTERACT_ACROSS_USERS_FULL" />
|
android:permission="android.permission.INTERACT_ACROSS_USERS_FULL" />
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
package org.lsposed.lspatch
|
package org.lsposed.lspatch
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.SharedPreferences
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
|
@ -15,6 +17,9 @@ class LSPApplication : Application() {
|
||||||
var shizukuBinderAvalable = false
|
var shizukuBinderAvalable = false
|
||||||
var shizukuGranted by mutableStateOf(false)
|
var shizukuGranted by mutableStateOf(false)
|
||||||
|
|
||||||
|
lateinit var appContext: Context
|
||||||
|
lateinit var prefs: SharedPreferences
|
||||||
|
|
||||||
init {
|
init {
|
||||||
Shizuku.addBinderReceivedListenerSticky {
|
Shizuku.addBinderReceivedListenerSticky {
|
||||||
shizukuBinderAvalable = true
|
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 android.provider.MediaStore
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import org.lsposed.lspatch.config.MyKeyStore
|
||||||
import org.lsposed.patch.LSPatch
|
import org.lsposed.patch.LSPatch
|
||||||
import org.lsposed.patch.util.Logger
|
import org.lsposed.patch.util.Logger
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
@ -29,7 +30,7 @@ object Patcher {
|
||||||
lateinit var outputPath: String
|
lateinit var outputPath: String
|
||||||
|
|
||||||
fun toStringArray(): Array<String> {
|
fun toStringArray(): Array<String> {
|
||||||
return arrayListOf<String>().run {
|
return buildList {
|
||||||
addAll(apkPaths)
|
addAll(apkPaths)
|
||||||
add("-o"); add(outputPath)
|
add("-o"); add(outputPath)
|
||||||
if (debuggable) add("-d")
|
if (debuggable) add("-d")
|
||||||
|
|
@ -43,9 +44,10 @@ object Patcher {
|
||||||
if (embeddedModules.isNotEmpty()) {
|
if (embeddedModules.isNotEmpty()) {
|
||||||
add("-m"); addAll(embeddedModules)
|
add("-m"); addAll(embeddedModules)
|
||||||
}
|
}
|
||||||
|
if (!MyKeyStore.useDefault) {
|
||||||
toTypedArray()
|
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(
|
Settings(
|
||||||
iconSelected = Icons.Filled.Settings,
|
iconSelected = Icons.Filled.Settings,
|
||||||
iconNotSelected = Icons.Outlined.Settings,
|
iconNotSelected = Icons.Outlined.Settings,
|
||||||
body = {}
|
body = { SettingsPage() }
|
||||||
),
|
),
|
||||||
NewPatch(
|
NewPatch(
|
||||||
body = { NewPatchPage(this) }
|
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_repo">Repo</string>
|
||||||
<string name="page_logs">Logs</string>
|
<string name="page_logs">Logs</string>
|
||||||
<string name="page_settings">Settings</string>
|
|
||||||
|
|
||||||
<!-- Home Page -->
|
<!-- Home Page -->
|
||||||
<string name="home_shizuku_available">Shizuku service available</string>
|
<string name="home_shizuku_available">Shizuku service available</string>
|
||||||
|
|
@ -48,4 +47,20 @@
|
||||||
|
|
||||||
<!-- Select Apps Page -->
|
<!-- Select Apps Page -->
|
||||||
<string name="page_select_apps">Select Apps</string>
|
<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>
|
</resources>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue