Support custom keystore for manager

This commit is contained in:
Nullptr 2022-04-05 17:21:31 +08:00
parent 02d6752895
commit dbe0df86ec
8 changed files with 312 additions and 14 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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