From d225f0bae99abb807402b08c10a09c39c5f7051c Mon Sep 17 00:00:00 2001 From: ShirkNeko <109797057+ShirkNeko@users.noreply.github.com> Date: Sun, 3 Aug 2025 05:39:35 +0800 Subject: [PATCH] manager: Continue to improve module signatures --- .../java/com/sukisu/ultra/ui/screen/Module.kt | 138 +++++++++++++++++- .../com/sukisu/ultra/ui/screen/Settings.kt | 14 ++ .../com/sukisu/ultra/ui/util/ModuleUtils.kt | 42 +++++- .../src/main/res/values-zh-rCN/strings.xml | 5 + manager/app/src/main/res/values/strings.xml | 5 + 5 files changed, 195 insertions(+), 9 deletions(-) diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Module.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Module.kt index b5722a74..80a4ea77 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Module.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Module.kt @@ -110,11 +110,18 @@ data class ModuleBottomSheetMenuItem( fun ModuleScreen(navigator: DestinationsNavigator) { val viewModel = viewModel() val context = LocalContext.current + val prefs = context.getSharedPreferences("settings",MODE_PRIVATE) val snackBarHost = LocalSnackbarHost.current val scope = rememberCoroutineScope() val confirmDialog = rememberConfirmDialog() var lastClickTime by remember { mutableStateOf(0L) } + // 签名验证弹窗状态 + var showSignatureDialog by remember { mutableStateOf(false) } + var signatureDialogMessage by remember { mutableStateOf("") } + var isForceVerificationFailed by remember { mutableStateOf(false) } + var pendingInstallAction by remember { mutableStateOf<(() -> Unit)?>(null) } + // 初始化缓存系统 LaunchedEffect(Unit) { viewModel.initializeCache(context) @@ -175,13 +182,51 @@ fun ModuleScreen(navigator: DestinationsNavigator) { ) if (confirmResult == ConfirmResult.Confirmed) { - try { - // 批量安装模块 - navigator.navigate(FlashScreenDestination(FlashIt.FlashModules(selectedModules))) - viewModel.markNeedRefresh() - } catch (e: Exception) { - Log.e("ModuleScreen", "Error navigating to FlashScreen: ${e.message}") - snackBarHost.showSnackbar("Error while installing module: ${e.message}") + // 验证模块签名 + val forceVerification = prefs.getBoolean("force_signature_verification", false) + val verificationResults = mutableMapOf() + + for (uri in selectedModules) { + val isVerified = verifyModuleSignature(context, uri) + verificationResults[uri] = isVerified + + if (forceVerification && !isVerified) { + withContext(Dispatchers.Main) { + signatureDialogMessage = context.getString(R.string.module_signature_invalid_message) + isForceVerificationFailed = true + showSignatureDialog = true + } + return@launch + } else if (!isVerified) { + withContext(Dispatchers.Main) { + signatureDialogMessage = context.getString(R.string.module_signature_verification_failed) + isForceVerificationFailed = false + pendingInstallAction = { + try { + navigator.navigate(FlashScreenDestination(FlashIt.FlashModules(selectedModules))) + viewModel.markNeedRefresh() + } catch (e: Exception) { + Log.e("ModuleScreen", "Error navigating to FlashScreen: ${e.message}") + scope.launch { + snackBarHost.showSnackbar("Error while installing module: ${e.message}") + } + } + } + showSignatureDialog = true + } + return@launch + } + } + + // 所有模块签名验证通过,直接安装 + if (verificationResults.all { it.value }) { + try { + navigator.navigate(FlashScreenDestination(FlashIt.FlashModules(selectedModules))) + viewModel.markNeedRefresh() + } catch (e: Exception) { + Log.e("ModuleScreen", "Error navigating to FlashScreen: ${e.message}") + snackBarHost.showSnackbar("Error while installing module: ${e.message}") + } } } } else { @@ -205,6 +250,26 @@ fun ModuleScreen(navigator: DestinationsNavigator) { ) if (confirmResult == ConfirmResult.Confirmed) { + // 验证模块签名 + val forceVerification = prefs.getBoolean("force_signature_verification", false) + val isVerified = verifyModuleSignature(context, uri) + + if (forceVerification && !isVerified) { + signatureDialogMessage = context.getString(R.string.module_signature_invalid_message) + isForceVerificationFailed = true + showSignatureDialog = true + return@launch + } else if (!isVerified) { + signatureDialogMessage = context.getString(R.string.module_signature_verification_failed) + isForceVerificationFailed = false + pendingInstallAction = { + navigator.navigate(FlashScreenDestination(FlashIt.FlashModule(uri))) + viewModel.markNeedRefresh() + } + showSignatureDialog = true + return@launch + } + navigator.navigate(FlashScreenDestination(FlashIt.FlashModule(uri))) viewModel.markNeedRefresh() } @@ -219,7 +284,6 @@ fun ModuleScreen(navigator: DestinationsNavigator) { val backupLauncher = ModuleModify.rememberModuleBackupLauncher(context, snackBarHost) val restoreLauncher = ModuleModify.rememberModuleRestoreLauncher(context, snackBarHost) - val prefs = context.getSharedPreferences("settings", MODE_PRIVATE) LaunchedEffect(Unit) { if (viewModel.moduleList.isEmpty() || viewModel.isNeedRefresh) { @@ -450,6 +514,64 @@ fun ModuleScreen(navigator: DestinationsNavigator) { ) } } + + // 签名验证弹窗 + if (showSignatureDialog) { + AlertDialog( + onDismissRequest = { showSignatureDialog = false }, + icon = { + Icon( + imageVector = Icons.Outlined.Warning, + contentDescription = null, + tint = MaterialTheme.colorScheme.error + ) + }, + title = { + Text( + text = stringResource(R.string.module_signature_invalid), + color = MaterialTheme.colorScheme.error + ) + }, + text = { + Text(text = signatureDialogMessage) + }, + confirmButton = { + if (isForceVerificationFailed) { + // 强制验证失败,只显示确定按钮 + TextButton( + onClick = { showSignatureDialog = false } + ) { + Text(stringResource(R.string.confirm)) + } + } else { + // 非强制验证失败,显示继续安装按钮 + TextButton( + onClick = { + showSignatureDialog = false + pendingInstallAction?.invoke() + pendingInstallAction = null + } + ) { + Text(stringResource(R.string.install)) + } + } + }, + dismissButton = if (!isForceVerificationFailed) { + { + TextButton( + onClick = { + showSignatureDialog = false + pendingInstallAction = null + } + ) { + Text(stringResource(R.string.cancel)) + } + } + } else { + null + } + ) + } } } diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Settings.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Settings.kt index 7b26b1ce..d0484fef 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Settings.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Settings.kt @@ -171,6 +171,20 @@ fun SettingScreen(navigator: DestinationsNavigator) { } ) } + // 强制签名验证开关 + var forceSignatureVerification by rememberSaveable { + mutableStateOf(prefs.getBoolean("force_signature_verification", false)) + } + SwitchItem( + icon = Icons.Filled.Security, + title = stringResource(R.string.module_signature_verification), + summary = stringResource(R.string.module_signature_verification_summary), + checked = forceSignatureVerification, + onCheckedChange = { enabled -> + prefs.edit { putBoolean("force_signature_verification", enabled) } + forceSignatureVerification = enabled + } + ) } ) } diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/util/ModuleUtils.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/util/ModuleUtils.kt index 42bc5055..332a100c 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/util/ModuleUtils.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/util/ModuleUtils.kt @@ -9,6 +9,9 @@ import java.nio.charset.StandardCharsets import java.util.zip.ZipInputStream import com.sukisu.ultra.R import android.util.Log +import com.sukisu.ultra.Natives +import java.io.File +import java.io.FileOutputStream import java.io.IOException object ModuleUtils { @@ -102,4 +105,41 @@ object ModuleUtils { Log.e(TAG, "Unable to get persistent permissions on URIs: $uri, Error: ${e.message}") } } -} \ No newline at end of file +} + +// 模块签名验证工具类 +object ModuleSignatureUtils { + private const val TAG = "ModuleSignatureUtils" + + fun verifyModuleSignature(context: Context, moduleUri: Uri): Boolean { + return try { + // 创建临时文件 + val tempFile = File(context.cacheDir, "temp_module_${System.currentTimeMillis()}.zip") + + // 复制URI内容到临时文件 + context.contentResolver.openInputStream(moduleUri)?.use { inputStream -> + FileOutputStream(tempFile).use { outputStream -> + inputStream.copyTo(outputStream) + } + } + + // 调用native方法验证签名 + val isVerified = Natives.verifyModuleSignature(tempFile.absolutePath) + + // 清理临时文件 + tempFile.delete() + + Log.d(TAG, "Module signature verification result: $isVerified") + isVerified + } catch (e: Exception) { + Log.e(TAG, "Error verifying module signature", e) + false + } + } + +} + +// 验证模块签名 +fun verifyModuleSignature(context: Context, moduleUri: Uri): Boolean { + return ModuleSignatureUtils.verifyModuleSignature(context, moduleUri) +} diff --git a/manager/app/src/main/res/values-zh-rCN/strings.xml b/manager/app/src/main/res/values-zh-rCN/strings.xml index f56db87b..ac06aa2f 100644 --- a/manager/app/src/main/res/values-zh-rCN/strings.xml +++ b/manager/app/src/main/res/values-zh-rCN/strings.xml @@ -606,4 +606,9 @@ SUS循环路径 循环路径配置 循环路径会在每次非root用户应用或隔离服务启动时重新标记为SUS_PATH。这有助于解决添加的路径可能因inode状态重置或内核中inode重新创建而失效的问题 + 模块签名验证 + 强制验证模块安装时的签名 + 模块签名验证未通过 + 由于安全设置,安装将被阻止 + 安装将继续,请注意模块是否存在未知加密脚本 diff --git a/manager/app/src/main/res/values/strings.xml b/manager/app/src/main/res/values/strings.xml index 5ae37bad..99791bb4 100644 --- a/manager/app/src/main/res/values/strings.xml +++ b/manager/app/src/main/res/values/strings.xml @@ -609,4 +609,9 @@ SUS Loop Path Loop Path Configuration 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. + Module signature verification + Force signature verification for module installation + Module signature verification failed + Installation will be blocked due to security settings + Installation will continue, please note the presence of unknown encryption scripts in the module