manager: Refinement of module signatures again
This commit is contained in:
@@ -70,11 +70,15 @@ data class ModuleInstallStatus(
|
|||||||
val totalModules: Int = 0,
|
val totalModules: Int = 0,
|
||||||
val currentModule: Int = 0,
|
val currentModule: Int = 0,
|
||||||
val currentModuleName: String = "",
|
val currentModuleName: String = "",
|
||||||
val failedModules: MutableList<String> = mutableListOf()
|
val failedModules: MutableList<String> = mutableListOf(),
|
||||||
|
val verifiedModules: MutableList<String> = mutableListOf() // 添加已验证模块列表
|
||||||
)
|
)
|
||||||
|
|
||||||
private var moduleInstallStatus = mutableStateOf(ModuleInstallStatus())
|
private var moduleInstallStatus = mutableStateOf(ModuleInstallStatus())
|
||||||
|
|
||||||
|
// 存储模块URI和验证状态的映射
|
||||||
|
private var moduleVerificationMap = mutableMapOf<Uri, Boolean>()
|
||||||
|
|
||||||
fun setFlashingStatus(status: FlashingStatus) {
|
fun setFlashingStatus(status: FlashingStatus) {
|
||||||
currentFlashingStatus.value = status
|
currentFlashingStatus.value = status
|
||||||
}
|
}
|
||||||
@@ -83,7 +87,8 @@ fun updateModuleInstallStatus(
|
|||||||
totalModules: Int? = null,
|
totalModules: Int? = null,
|
||||||
currentModule: Int? = null,
|
currentModule: Int? = null,
|
||||||
currentModuleName: String? = null,
|
currentModuleName: String? = null,
|
||||||
failedModule: String? = null
|
failedModule: String? = null,
|
||||||
|
verifiedModule: String? = null
|
||||||
) {
|
) {
|
||||||
val current = moduleInstallStatus.value
|
val current = moduleInstallStatus.value
|
||||||
moduleInstallStatus.value = current.copy(
|
moduleInstallStatus.value = current.copy(
|
||||||
@@ -99,6 +104,18 @@ fun updateModuleInstallStatus(
|
|||||||
failedModules = updatedFailedModules
|
failedModules = updatedFailedModules
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (verifiedModule != null) {
|
||||||
|
val updatedVerifiedModules = current.verifiedModules.toMutableList()
|
||||||
|
updatedVerifiedModules.add(verifiedModule)
|
||||||
|
moduleInstallStatus.value = moduleInstallStatus.value.copy(
|
||||||
|
verifiedModules = updatedVerifiedModules
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setModuleVerificationStatus(uri: Uri, isVerified: Boolean) {
|
||||||
|
moduleVerificationMap[uri] = isVerified
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@@ -142,6 +159,7 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
|
|||||||
)
|
)
|
||||||
hasFlashCompleted = false
|
hasFlashCompleted = false
|
||||||
hasExecuted = false
|
hasExecuted = false
|
||||||
|
moduleVerificationMap.clear()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is FlashIt.FlashModuleUpdate -> {
|
is FlashIt.FlashModuleUpdate -> {
|
||||||
@@ -179,6 +197,11 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
|
|||||||
setFlashingStatus(FlashingStatus.FAILED)
|
setFlashingStatus(FlashingStatus.FAILED)
|
||||||
} else {
|
} else {
|
||||||
setFlashingStatus(FlashingStatus.SUCCESS)
|
setFlashingStatus(FlashingStatus.SUCCESS)
|
||||||
|
|
||||||
|
// 处理模块更新成功后的验证标志
|
||||||
|
val isVerified = moduleVerificationMap[flashIt.uri] ?: false
|
||||||
|
ModuleOperationUtils.handleModuleUpdate(context, flashIt.uri, isVerified)
|
||||||
|
|
||||||
viewModel.markNeedRefresh()
|
viewModel.markNeedRefresh()
|
||||||
}
|
}
|
||||||
if (showReboot) {
|
if (showReboot) {
|
||||||
@@ -239,6 +262,28 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
setFlashingStatus(FlashingStatus.SUCCESS)
|
setFlashingStatus(FlashingStatus.SUCCESS)
|
||||||
|
|
||||||
|
// 处理模块安装成功后的验证标志
|
||||||
|
when (flashIt) {
|
||||||
|
is FlashIt.FlashModule -> {
|
||||||
|
val isVerified = moduleVerificationMap[flashIt.uri] ?: false
|
||||||
|
ModuleOperationUtils.handleModuleInstallSuccess(context, flashIt.uri, isVerified)
|
||||||
|
if (isVerified) {
|
||||||
|
updateModuleInstallStatus(verifiedModule = moduleInstallStatus.value.currentModuleName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is FlashIt.FlashModules -> {
|
||||||
|
val currentUri = flashIt.uris[flashIt.currentIndex]
|
||||||
|
val isVerified = moduleVerificationMap[currentUri] ?: false
|
||||||
|
ModuleOperationUtils.handleModuleInstallSuccess(context, currentUri, isVerified)
|
||||||
|
if (isVerified) {
|
||||||
|
updateModuleInstallStatus(verifiedModule = moduleInstallStatus.value.currentModuleName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
|
||||||
viewModel.markNeedRefresh()
|
viewModel.markNeedRefresh()
|
||||||
}
|
}
|
||||||
if (showReboot) {
|
if (showReboot) {
|
||||||
|
|||||||
@@ -189,6 +189,8 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
|
|||||||
for (uri in selectedModules) {
|
for (uri in selectedModules) {
|
||||||
val isVerified = verifyModuleSignature(context, uri)
|
val isVerified = verifyModuleSignature(context, uri)
|
||||||
verificationResults[uri] = isVerified
|
verificationResults[uri] = isVerified
|
||||||
|
// 存储验证状态
|
||||||
|
setModuleVerificationStatus(uri, isVerified)
|
||||||
|
|
||||||
if (forceVerification && !isVerified) {
|
if (forceVerification && !isVerified) {
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
@@ -253,6 +255,8 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
|
|||||||
// 验证模块签名
|
// 验证模块签名
|
||||||
val forceVerification = prefs.getBoolean("force_signature_verification", false)
|
val forceVerification = prefs.getBoolean("force_signature_verification", false)
|
||||||
val isVerified = verifyModuleSignature(context, uri)
|
val isVerified = verifyModuleSignature(context, uri)
|
||||||
|
// 存储验证状态
|
||||||
|
setModuleVerificationStatus(uri, isVerified)
|
||||||
|
|
||||||
if (forceVerification && !isVerified) {
|
if (forceVerification && !isVerified) {
|
||||||
signatureDialogMessage = context.getString(R.string.module_signature_invalid_message)
|
signatureDialogMessage = context.getString(R.string.module_signature_invalid_message)
|
||||||
@@ -835,7 +839,12 @@ private fun ModuleList(
|
|||||||
downloadUrl,
|
downloadUrl,
|
||||||
fileName,
|
fileName,
|
||||||
downloading,
|
downloading,
|
||||||
onDownloaded = onUpdateModule,
|
onDownloaded = { uri ->
|
||||||
|
// 验证更新模块的签名
|
||||||
|
val isVerified = verifyModuleSignature(context, uri)
|
||||||
|
setModuleVerificationStatus(uri, isVerified)
|
||||||
|
onUpdateModule(uri)
|
||||||
|
},
|
||||||
onDownloading = {
|
onDownloading = {
|
||||||
launch(Dispatchers.Main) {
|
launch(Dispatchers.Main) {
|
||||||
Toast.makeText(context, downloading, Toast.LENGTH_SHORT).show()
|
Toast.makeText(context, downloading, Toast.LENGTH_SHORT).show()
|
||||||
@@ -867,6 +876,8 @@ private fun ModuleList(
|
|||||||
val success = loadingDialog.withLoading {
|
val success = loadingDialog.withLoading {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
if (isUninstall) {
|
if (isUninstall) {
|
||||||
|
// 卸载时移除验证标志
|
||||||
|
ModuleOperationUtils.handleModuleUninstall(module.dirId)
|
||||||
uninstallModule(module.dirId)
|
uninstallModule(module.dirId)
|
||||||
} else {
|
} else {
|
||||||
restoreModule(module.dirId)
|
restoreModule(module.dirId)
|
||||||
@@ -1075,14 +1086,48 @@ fun ModuleItem(
|
|||||||
Column(
|
Column(
|
||||||
modifier = Modifier.fillMaxWidth(0.8f)
|
modifier = Modifier.fillMaxWidth(0.8f)
|
||||||
) {
|
) {
|
||||||
Text(
|
Row(
|
||||||
text = module.name,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
fontSize = MaterialTheme.typography.titleMedium.fontSize,
|
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
fontWeight = FontWeight.SemiBold,
|
) {
|
||||||
lineHeight = MaterialTheme.typography.bodySmall.lineHeight,
|
Text(
|
||||||
fontFamily = MaterialTheme.typography.titleMedium.fontFamily,
|
text = module.name,
|
||||||
textDecoration = textDecoration,
|
fontSize = MaterialTheme.typography.titleMedium.fontSize,
|
||||||
)
|
fontWeight = FontWeight.SemiBold,
|
||||||
|
lineHeight = MaterialTheme.typography.bodySmall.lineHeight,
|
||||||
|
fontFamily = MaterialTheme.typography.titleMedium.fontFamily,
|
||||||
|
textDecoration = textDecoration,
|
||||||
|
modifier = Modifier.weight(1f, false)
|
||||||
|
)
|
||||||
|
|
||||||
|
// 显示验证标签
|
||||||
|
if (module.isVerified) {
|
||||||
|
Surface(
|
||||||
|
shape = RoundedCornerShape(12.dp),
|
||||||
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
modifier = Modifier
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier.padding(horizontal = 6.dp, vertical = 2.dp)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Verified,
|
||||||
|
contentDescription = stringResource(R.string.module_signature_verified),
|
||||||
|
tint = MaterialTheme.colorScheme.onPrimary,
|
||||||
|
modifier = Modifier.size(12.dp)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(2.dp))
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.module_verified),
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
color = MaterialTheme.colorScheme.onPrimary,
|
||||||
|
fontWeight = FontWeight.Medium
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = "$moduleVersion: ${module.version}",
|
text = "$moduleVersion: ${module.version}",
|
||||||
@@ -1309,6 +1354,8 @@ fun ModuleItemPreview() {
|
|||||||
hasActionScript = false,
|
hasActionScript = false,
|
||||||
dirId = "dirId",
|
dirId = "dirId",
|
||||||
config = ModuleConfig(),
|
config = ModuleConfig(),
|
||||||
|
isVerified = true,
|
||||||
|
verificationTimestamp = System.currentTimeMillis()
|
||||||
)
|
)
|
||||||
ModuleItem(EmptyDestinationsNavigator, module, "", {}, {}, {}, {})
|
ModuleItem(EmptyDestinationsNavigator, module, "", {}, {}, {}, {})
|
||||||
}
|
}
|
||||||
@@ -36,7 +36,7 @@ object ModuleUtils {
|
|||||||
}
|
}
|
||||||
}?.removeSuffix(".zip") ?: context.getString(R.string.unknown_module)
|
}?.removeSuffix(".zip") ?: context.getString(R.string.unknown_module)
|
||||||
|
|
||||||
var formattedFileName = fileName.replace(Regex("[^a-zA-Z0-9\\s\\-_.@()\\u4e00-\\u9fa5]"), "").trim()
|
val formattedFileName = fileName.replace(Regex("[^a-zA-Z0-9\\s\\-_.@()\\u4e00-\\u9fa5]"), "").trim()
|
||||||
var moduleName = formattedFileName
|
var moduleName = formattedFileName
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -55,12 +55,10 @@ object ModuleUtils {
|
|||||||
if (entry.name == "module.prop") {
|
if (entry.name == "module.prop") {
|
||||||
val reader = BufferedReader(InputStreamReader(zipInputStream, StandardCharsets.UTF_8))
|
val reader = BufferedReader(InputStreamReader(zipInputStream, StandardCharsets.UTF_8))
|
||||||
var line: String?
|
var line: String?
|
||||||
var nameFound = false
|
|
||||||
while (reader.readLine().also { line = it } != null) {
|
while (reader.readLine().also { line = it } != null) {
|
||||||
if (line?.startsWith("name=") == true) {
|
if (line?.startsWith("name=") == true) {
|
||||||
moduleName = line.substringAfter("=")
|
moduleName = line.substringAfter("=")
|
||||||
moduleName = moduleName.replace(Regex("[^a-zA-Z0-9\\s\\-_.@()\\u4e00-\\u9fa5]"), "").trim()
|
moduleName = moduleName.replace(Regex("[^a-zA-Z0-9\\s\\-_.@()\\u4e00-\\u9fa5]"), "").trim()
|
||||||
nameFound = true
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -105,6 +103,45 @@ object ModuleUtils {
|
|||||||
Log.e(TAG, "Unable to get persistent permissions on URIs: $uri, Error: ${e.message}")
|
Log.e(TAG, "Unable to get persistent permissions on URIs: $uri, Error: ${e.message}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun extractModuleId(context: Context, uri: Uri): String? {
|
||||||
|
if (uri == Uri.EMPTY) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return try {
|
||||||
|
|
||||||
|
val inputStream = context.contentResolver.openInputStream(uri)
|
||||||
|
if (inputStream == null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val zipInputStream = ZipInputStream(inputStream)
|
||||||
|
var entry = zipInputStream.nextEntry
|
||||||
|
var moduleId: String? = null
|
||||||
|
|
||||||
|
// 遍历ZIP文件中的条目,查找module.prop文件
|
||||||
|
while (entry != null) {
|
||||||
|
if (entry.name == "module.prop") {
|
||||||
|
val reader = BufferedReader(InputStreamReader(zipInputStream, StandardCharsets.UTF_8))
|
||||||
|
var line: String?
|
||||||
|
while (reader.readLine().also { line = it } != null) {
|
||||||
|
if (line?.startsWith("id=") == true) {
|
||||||
|
moduleId = line.substringAfter("=").trim()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
entry = zipInputStream.nextEntry
|
||||||
|
}
|
||||||
|
zipInputStream.close()
|
||||||
|
moduleId
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "提取模块ID时发生异常: ${e.message}", e)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 模块签名验证工具类
|
// 模块签名验证工具类
|
||||||
@@ -143,3 +180,71 @@ object ModuleSignatureUtils {
|
|||||||
fun verifyModuleSignature(context: Context, moduleUri: Uri): Boolean {
|
fun verifyModuleSignature(context: Context, moduleUri: Uri): Boolean {
|
||||||
return ModuleSignatureUtils.verifyModuleSignature(context, moduleUri)
|
return ModuleSignatureUtils.verifyModuleSignature(context, moduleUri)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
object ModuleOperationUtils {
|
||||||
|
private const val TAG = "ModuleOperationUtils"
|
||||||
|
|
||||||
|
fun handleModuleInstallSuccess(context: Context, moduleUri: Uri, isSignatureVerified: Boolean) {
|
||||||
|
if (!isSignatureVerified) {
|
||||||
|
Log.d(TAG, "模块签名未验证,跳过创建验证标志")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 从ZIP文件提取模块ID
|
||||||
|
val moduleId = ModuleUtils.extractModuleId(context, moduleUri)
|
||||||
|
if (moduleId == null) {
|
||||||
|
Log.e(TAG, "无法提取模块ID,无法创建验证标志")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建验证标志文件
|
||||||
|
val success = ModuleVerificationManager.createVerificationFlag(moduleId)
|
||||||
|
if (success) {
|
||||||
|
Log.d(TAG, "模块 $moduleId 验证标志创建成功")
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "模块 $moduleId 验证标志创建失败")
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "处理模块安装成功时发生异常", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun handleModuleUninstall(moduleId: String) {
|
||||||
|
try {
|
||||||
|
val success = ModuleVerificationManager.removeVerificationFlag(moduleId)
|
||||||
|
if (success) {
|
||||||
|
Log.d(TAG, "模块 $moduleId 验证标志移除成功")
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, "模块 $moduleId 验证标志移除失败或不存在")
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "处理模块卸载时发生异常: $moduleId", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun handleModuleUpdate(context: Context, moduleUri: Uri, isSignatureVerified: Boolean) {
|
||||||
|
try {
|
||||||
|
val moduleId = ModuleUtils.extractModuleId(context, moduleUri)
|
||||||
|
if (moduleId == null) {
|
||||||
|
Log.e(TAG, "无法提取模块ID,无法处理验证标志")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSignatureVerified) {
|
||||||
|
// 签名验证通过,创建或更新验证标志
|
||||||
|
val success = ModuleVerificationManager.createVerificationFlag(moduleId)
|
||||||
|
if (success) {
|
||||||
|
Log.d(TAG, "模块 $moduleId 更新后验证标志已更新")
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "模块 $moduleId 更新后验证标志更新失败")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 签名验证失败,移除验证标志
|
||||||
|
ModuleVerificationManager.removeVerificationFlag(moduleId)
|
||||||
|
Log.d(TAG, "模块 $moduleId 更新后签名未验证,验证标志已移除")
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "处理模块更新时发生异常", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
package com.sukisu.ultra.ui.util
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
|
||||||
|
/**
|
||||||
|
|
||||||
|
* @author ShirkNeko
|
||||||
|
* @date 2025/8/3
|
||||||
|
*/
|
||||||
|
object ModuleVerificationManager {
|
||||||
|
private const val TAG = "ModuleVerificationManager"
|
||||||
|
private const val VERIFICATION_FLAGS_DIR = "/data/adb/ksu/verified_modules"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 为指定模块创建验证标志文件
|
||||||
|
*
|
||||||
|
* @param moduleId 模块文件夹名称
|
||||||
|
* @return 是否成功创建标志文件
|
||||||
|
*/
|
||||||
|
fun createVerificationFlag(moduleId: String): Boolean {
|
||||||
|
return try {
|
||||||
|
val shell = getRootShell()
|
||||||
|
val flagFilePath = "$VERIFICATION_FLAGS_DIR/$moduleId"
|
||||||
|
|
||||||
|
// 确保目录存在
|
||||||
|
val createDirCommand = "mkdir -p '$VERIFICATION_FLAGS_DIR'"
|
||||||
|
shell.newJob().add(createDirCommand).exec()
|
||||||
|
|
||||||
|
// 创建验证标志文件,写入验证时间戳
|
||||||
|
val timestamp = System.currentTimeMillis()
|
||||||
|
val command = "echo '$timestamp' > '$flagFilePath'"
|
||||||
|
|
||||||
|
val result = shell.newJob().add(command).exec()
|
||||||
|
|
||||||
|
if (result.isSuccess) {
|
||||||
|
Log.d(TAG, "验证标志文件创建成功: $flagFilePath")
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "验证标志文件创建失败: $moduleId")
|
||||||
|
false
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "创建验证标志文件时发生异常: $moduleId", e)
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeVerificationFlag(moduleId: String): Boolean {
|
||||||
|
return try {
|
||||||
|
val shell = getRootShell()
|
||||||
|
val flagFilePath = "$VERIFICATION_FLAGS_DIR/$moduleId"
|
||||||
|
|
||||||
|
val command = "rm -f '$flagFilePath'"
|
||||||
|
val result = shell.newJob().add(command).exec()
|
||||||
|
|
||||||
|
if (result.isSuccess) {
|
||||||
|
Log.d(TAG, "验证标志文件移除成功: $flagFilePath")
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "验证标志文件移除失败: $moduleId")
|
||||||
|
false
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "移除验证标志文件时发生异常: $moduleId", e)
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getVerificationTimestamp(moduleId: String): Long {
|
||||||
|
return try {
|
||||||
|
val shell = getRootShell()
|
||||||
|
val flagFilePath = "$VERIFICATION_FLAGS_DIR/$moduleId"
|
||||||
|
|
||||||
|
val command = "cat '$flagFilePath' 2>/dev/null || echo '0'"
|
||||||
|
val result = shell.newJob().add(command).to(ArrayList(), null).exec()
|
||||||
|
|
||||||
|
if (result.isSuccess && result.out.isNotEmpty()) {
|
||||||
|
val timestampStr = result.out.firstOrNull()?.trim() ?: "0"
|
||||||
|
timestampStr.toLongOrNull() ?: 0L
|
||||||
|
} else {
|
||||||
|
0L
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "获取验证时间戳时发生异常: $moduleId", e)
|
||||||
|
0L
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun batchCheckVerificationStatus(moduleIds: List<String>): Map<String, Boolean> {
|
||||||
|
if (moduleIds.isEmpty()) return emptyMap()
|
||||||
|
|
||||||
|
return try {
|
||||||
|
val shell = getRootShell()
|
||||||
|
val result = mutableMapOf<String, Boolean>()
|
||||||
|
|
||||||
|
// 确保目录存在
|
||||||
|
val createDirCommand = "mkdir -p '$VERIFICATION_FLAGS_DIR'"
|
||||||
|
shell.newJob().add(createDirCommand).exec()
|
||||||
|
|
||||||
|
// 批量检查所有模块的验证标志文件
|
||||||
|
val commands = moduleIds.map { moduleId ->
|
||||||
|
"test -f '$VERIFICATION_FLAGS_DIR/$moduleId' && echo '$moduleId:true' || echo '$moduleId:false'"
|
||||||
|
}
|
||||||
|
|
||||||
|
val command = commands.joinToString(" && ")
|
||||||
|
val shellResult = shell.newJob().add(command).to(ArrayList(), null).exec()
|
||||||
|
|
||||||
|
if (shellResult.isSuccess) {
|
||||||
|
shellResult.out.forEach { line ->
|
||||||
|
val parts = line.split(":")
|
||||||
|
if (parts.size == 2) {
|
||||||
|
val moduleId = parts[0]
|
||||||
|
val isVerified = parts[1] == "true"
|
||||||
|
result[moduleId] = isVerified
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d(TAG, "批量验证检查完成,共检查 ${moduleIds.size} 个模块")
|
||||||
|
result
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "批量检查验证状态时发生异常", e)
|
||||||
|
// 返回默认值,所有模块都标记为未验证
|
||||||
|
moduleIds.associateWith { false }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,6 +16,7 @@ import kotlinx.coroutines.launch
|
|||||||
import com.sukisu.ultra.ui.util.HanziToPinyin
|
import com.sukisu.ultra.ui.util.HanziToPinyin
|
||||||
import com.sukisu.ultra.ui.util.listModules
|
import com.sukisu.ultra.ui.util.listModules
|
||||||
import com.sukisu.ultra.ui.util.getRootShell
|
import com.sukisu.ultra.ui.util.getRootShell
|
||||||
|
import com.sukisu.ultra.ui.util.ModuleVerificationManager
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.json.JSONArray
|
import org.json.JSONArray
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
@@ -86,6 +87,8 @@ class ModuleViewModel : ViewModel() {
|
|||||||
val hasActionScript: Boolean,
|
val hasActionScript: Boolean,
|
||||||
val dirId: String, // real module id (dir name)
|
val dirId: String, // real module id (dir name)
|
||||||
var config: ModuleConfig? = null,
|
var config: ModuleConfig? = null,
|
||||||
|
var isVerified: Boolean = false, // 添加验证状态字段
|
||||||
|
var verificationTimestamp: Long = 0L, // 添加验证时间戳
|
||||||
)
|
)
|
||||||
|
|
||||||
var isRefreshing by mutableStateOf(false)
|
var isRefreshing by mutableStateOf(false)
|
||||||
@@ -131,7 +134,7 @@ class ModuleViewModel : ViewModel() {
|
|||||||
Log.i(TAG, "result: $result")
|
Log.i(TAG, "result: $result")
|
||||||
|
|
||||||
val array = JSONArray(result)
|
val array = JSONArray(result)
|
||||||
modules = (0 until array.length())
|
val moduleInfos = (0 until array.length())
|
||||||
.asSequence()
|
.asSequence()
|
||||||
.map { array.getJSONObject(it) }
|
.map { array.getJSONObject(it) }
|
||||||
.map { obj ->
|
.map { obj ->
|
||||||
@@ -151,6 +154,26 @@ class ModuleViewModel : ViewModel() {
|
|||||||
obj.getString("dir_id")
|
obj.getString("dir_id")
|
||||||
)
|
)
|
||||||
}.toList()
|
}.toList()
|
||||||
|
|
||||||
|
// 批量检查所有模块的验证状态
|
||||||
|
val moduleIds = moduleInfos.map { it.dirId }
|
||||||
|
val verificationStatus = ModuleVerificationManager.batchCheckVerificationStatus(moduleIds)
|
||||||
|
|
||||||
|
// 更新模块验证状态
|
||||||
|
modules = moduleInfos.map { moduleInfo ->
|
||||||
|
val isVerified = verificationStatus[moduleInfo.dirId] ?: false
|
||||||
|
val verificationTimestamp = if (isVerified) {
|
||||||
|
ModuleVerificationManager.getVerificationTimestamp(moduleInfo.dirId)
|
||||||
|
} else {
|
||||||
|
0L
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleInfo.copy(
|
||||||
|
isVerified = isVerified,
|
||||||
|
verificationTimestamp = verificationTimestamp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
launch {
|
launch {
|
||||||
modules.forEach { module ->
|
modules.forEach { module ->
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
@@ -207,6 +230,14 @@ class ModuleViewModel : ViewModel() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun createModuleVerificationFlag(moduleId: String): Boolean {
|
||||||
|
return ModuleVerificationManager.createVerificationFlag(moduleId)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeModuleVerificationFlag(moduleId: String): Boolean {
|
||||||
|
return ModuleVerificationManager.removeVerificationFlag(moduleId)
|
||||||
|
}
|
||||||
|
|
||||||
private fun sanitizeVersionString(version: String): String {
|
private fun sanitizeVersionString(version: String): String {
|
||||||
return version.replace(Regex("[^a-zA-Z0-9.\\-_]"), "_")
|
return version.replace(Regex("[^a-zA-Z0-9.\\-_]"), "_")
|
||||||
}
|
}
|
||||||
@@ -269,6 +300,31 @@ class ModuleViewModel : ViewModel() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun ModuleViewModel.ModuleInfo.copy(
|
||||||
|
id: String = this.id,
|
||||||
|
name: String = this.name,
|
||||||
|
author: String = this.author,
|
||||||
|
version: String = this.version,
|
||||||
|
versionCode: Int = this.versionCode,
|
||||||
|
description: String = this.description,
|
||||||
|
enabled: Boolean = this.enabled,
|
||||||
|
update: Boolean = this.update,
|
||||||
|
remove: Boolean = this.remove,
|
||||||
|
updateJson: String = this.updateJson,
|
||||||
|
hasWebUi: Boolean = this.hasWebUi,
|
||||||
|
hasActionScript: Boolean = this.hasActionScript,
|
||||||
|
dirId: String = this.dirId,
|
||||||
|
config: ModuleConfig? = this.config,
|
||||||
|
isVerified: Boolean = this.isVerified,
|
||||||
|
verificationTimestamp: Long = this.verificationTimestamp
|
||||||
|
): ModuleViewModel.ModuleInfo {
|
||||||
|
return ModuleViewModel.ModuleInfo(
|
||||||
|
id, name, author, version, versionCode, description,
|
||||||
|
enabled, update, remove, updateJson, hasWebUi, hasActionScript,
|
||||||
|
dirId, config, isVerified, verificationTimestamp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 模块大小缓存管理器
|
* 模块大小缓存管理器
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -539,8 +539,8 @@
|
|||||||
<string name="susfs_backup_info_date">Ngày sao lưu: %s</string>
|
<string name="susfs_backup_info_date">Ngày sao lưu: %s</string>
|
||||||
<string name="susfs_backup_info_device">Thiết bị: %s</string>
|
<string name="susfs_backup_info_device">Thiết bị: %s</string>
|
||||||
<string name="susfs_backup_info_version">Phiên bản: %s</string>
|
<string name="susfs_backup_info_version">Phiên bản: %s</string>
|
||||||
<string name="hide_bl_script">Trạng thái khóa</string>
|
<string name="hide_bl_script">Trạng thái Lock BL</string>
|
||||||
<string name="hide_bl_script_description">Ghi đè thuộc tính trạng thái khóa khởi động ở chế độ dịch vụ late_start</string>
|
<string name="hide_bl_script_description">Ghi đè thuộc tính trạng thái lock bootloader ở chế độ dịch vụ late_start</string>
|
||||||
<string name="cleanup_residue">Dọn rác</string>
|
<string name="cleanup_residue">Dọn rác</string>
|
||||||
<string name="cleanup_residue_description">Dọn dẹp các file và folder còn sót lại của các module và công cụ (Có thể bị xóa nhầm, dẫn đến mất dữ liệu và không khởi động được)</string>
|
<string name="cleanup_residue_description">Dọn dẹp các file và folder còn sót lại của các module và công cụ (Có thể bị xóa nhầm, dẫn đến mất dữ liệu và không khởi động được)</string>
|
||||||
<string name="susfs_edit_sus_path">Chỉnh sửa Đường dẫn SuS</string>
|
<string name="susfs_edit_sus_path">Chỉnh sửa Đường dẫn SuS</string>
|
||||||
@@ -607,9 +607,9 @@
|
|||||||
<string name="sus_loop_path_feature_label">Đường dẫn Vòng lặp SuS</string>
|
<string name="sus_loop_path_feature_label">Đường dẫn Vòng lặp SuS</string>
|
||||||
<string name="sus_loop_paths_description_title">Cấu hình Đường dẫn Vòng lặp</string>
|
<string name="sus_loop_paths_description_title">Cấu hình Đường dẫn Vòng lặp</string>
|
||||||
<string name="sus_loop_paths_description_text">Đường dẫn Vòng lặp được đổi tên thành SUS_PATH mỗi khi một ứng dụng không phải root hoặc dịch vụ cô lập được khởi động. Điều này giúp giải quyết vấn đề đường dẫn đã thêm có thể trở nên không hợp lệ do trạng thái inode được đặt lại hoặc inode được tạo lại trong Kernel</string>
|
<string name="sus_loop_paths_description_text">Đường dẫn Vòng lặp được đổi tên thành SUS_PATH mỗi khi một ứng dụng không phải root hoặc dịch vụ cô lập được khởi động. Điều này giúp giải quyết vấn đề đường dẫn đã thêm có thể trở nên không hợp lệ do trạng thái inode được đặt lại hoặc inode được tạo lại trong Kernel</string>
|
||||||
<string name="module_signature_verification">Xác minh chữ ký module</string>
|
<string name="module_signature_verification">Xác minh chữ ký</string>
|
||||||
<string name="module_signature_verification_summary">Buộc xác minh chữ ký khi cài đặt module</string>
|
<string name="module_signature_verification_summary">Buộc xác minh chữ ký khi cài đặt module (Chỉ khả dụng cho arm64-v8a)</string>
|
||||||
<string name="module_signature_invalid">Xác minh chữ ký module thất bại</string>
|
<string name="module_signature_invalid">Tác giả không xác định</string>
|
||||||
<string name="module_signature_invalid_message">Cài đặt sẽ bị chặn do cài đặt bảo mật</string>
|
<string name="module_signature_invalid_message">Các module chưa được ký có thể chưa hoàn chỉnh. Để bảo vệ thiết bị của bạn, module này đã bị chặn cài đặt</string>
|
||||||
<string name="module_signature_verification_failed">Quá trình cài đặt sẽ tiếp tục, vui lòng chú ý xem có tập lệnh mã hóa không xác định nào trong module hay không</string>
|
<string name="module_signature_verification_failed">Các module chưa được ký có thể chưa hoàn chỉnh. Bạn có muốn cài đặt module này từ một tác giả chưa xác định không?</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -606,6 +606,9 @@
|
|||||||
<string name="sus_loop_path_feature_label">SUS循环路径</string>
|
<string name="sus_loop_path_feature_label">SUS循环路径</string>
|
||||||
<string name="sus_loop_paths_description_title">循环路径配置</string>
|
<string name="sus_loop_paths_description_title">循环路径配置</string>
|
||||||
<string name="sus_loop_paths_description_text">循环路径会在每次非root用户应用或隔离服务启动时重新标记为SUS_PATH。这有助于解决添加的路径可能因inode状态重置或内核中inode重新创建而失效的问题</string>
|
<string name="sus_loop_paths_description_text">循环路径会在每次非root用户应用或隔离服务启动时重新标记为SUS_PATH。这有助于解决添加的路径可能因inode状态重置或内核中inode重新创建而失效的问题</string>
|
||||||
|
<!-- 模块签名功能描述 -->
|
||||||
|
<string name="module_verified">已验证</string>
|
||||||
|
<string name="module_signature_verified">模块签名已验证</string>
|
||||||
<string name="module_signature_verification">验证签名</string>
|
<string name="module_signature_verification">验证签名</string>
|
||||||
<string name="module_signature_verification_summary">模块安装时,强制验证签名。(仅 arm64-v8a 可用)</string>
|
<string name="module_signature_verification_summary">模块安装时,强制验证签名。(仅 arm64-v8a 可用)</string>
|
||||||
<string name="module_signature_invalid">未知发布者</string>
|
<string name="module_signature_invalid">未知发布者</string>
|
||||||
|
|||||||
@@ -609,6 +609,9 @@
|
|||||||
<string name="sus_loop_path_feature_label">SUS Loop Path</string>
|
<string name="sus_loop_path_feature_label">SUS Loop Path</string>
|
||||||
<string name="sus_loop_paths_description_title">Loop Path Configuration</string>
|
<string name="sus_loop_paths_description_title">Loop Path Configuration</string>
|
||||||
<string name="sus_loop_paths_description_text">Loop paths are re-flagged as SUS_PATH on each non-root user app or isolated service startup. This helps address issues where added paths may have their inode status reset or inode re-created in the kernel.</string>
|
<string name="sus_loop_paths_description_text">Loop paths are re-flagged as SUS_PATH on each non-root user app or isolated service startup. This helps address issues where added paths may have their inode status reset or inode re-created in the kernel.</string>
|
||||||
|
<!-- 模块签名功能描述 -->
|
||||||
|
<string name="module_verified">Validated</string>
|
||||||
|
<string name="module_signature_verified">Module signature verified</string>
|
||||||
<string name="module_signature_verification">Signature Verification</string>
|
<string name="module_signature_verification">Signature Verification</string>
|
||||||
<string name="module_signature_verification_summary">Force signature verification when installing modules. (Only available for arm64-v8a)</string>
|
<string name="module_signature_verification_summary">Force signature verification when installing modules. (Only available for arm64-v8a)</string>
|
||||||
<string name="module_signature_invalid">Unknown publisher</string>
|
<string name="module_signature_invalid">Unknown publisher</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user