From 478572bc949c66b8b5f12dc4eb92bec0c3a702b7 Mon Sep 17 00:00:00 2001 From: "Creater. Bai" <1302064396@qq.com> Date: Mon, 26 Oct 2026 20:15:59 +0800 Subject: [PATCH 1/5] refactor(install): Prepare type-aware local package installation Add package type to signature metadata and cache-backed install entry points for plugins and environments. Expose signature helpers internally. --- .../environment/InstallEnvironmentUseCase.kt | 11 +++++++++++ .../application/plugin/InstallPluginUseCase.kt | 11 +++++++++++ .../signature/gateway/PackageSignatureGatewayImpl.kt | 9 ++++++--- .../domain/install/model/PackageSignatureInfo.kt | 1 + 4 files changed, 29 insertions(+), 3 deletions(-) diff --git a/application/src/main/kotlin/com/baidaidai/rootless_store/application/environment/InstallEnvironmentUseCase.kt b/application/src/main/kotlin/com/baidaidai/rootless_store/application/environment/InstallEnvironmentUseCase.kt index 5ab3f1d4..9d7413b7 100644 --- a/application/src/main/kotlin/com/baidaidai/rootless_store/application/environment/InstallEnvironmentUseCase.kt +++ b/application/src/main/kotlin/com/baidaidai/rootless_store/application/environment/InstallEnvironmentUseCase.kt @@ -1,6 +1,7 @@ package com.baidaidai.rootless_store.application.environment import com.baidaidai.rootless_store.core.util.formatAsMultilineString +import com.baidaidai.rootless_store.data.fileSystem.gateway.AndroidFileSystemDefaultOperatorGatewayImpl import com.baidaidai.rootless_store.data.environment.gateway.EnvironmentGatewayImpl import com.baidaidai.rootless_store.data.environment.repository.EnvironmentRepositoryImpl import com.baidaidai.rootless_store.data.environment.repository.EnvironmentStatusRepositoryImpl @@ -14,10 +15,20 @@ import javax.inject.Inject import java.io.File class InstallEnvironmentUseCase @Inject constructor( + private val androidFileSystemDefaultOperatorGatewayImpl: AndroidFileSystemDefaultOperatorGatewayImpl, private val environmentGatewayImpl: EnvironmentGatewayImpl, private val environmentRepositoryImpl: EnvironmentRepositoryImpl, private val environmentStatusRepositoryImpl: EnvironmentStatusRepositoryImpl ) { + + suspend operator fun invoke(): Result { + val packageDirectory = androidFileSystemDefaultOperatorGatewayImpl + .getExternalAppPackageCacheDirectoryFile() + val localPackageFile = File(packageDirectory, "package.zip") + + return invoke(localPackageFile) + } + suspend operator fun invoke(packageFile: File): Result { val environmentManifest: EnvironmentManifest = environmentGatewayImpl .parseEnvironmentManifest(packageFile) diff --git a/application/src/main/kotlin/com/baidaidai/rootless_store/application/plugin/InstallPluginUseCase.kt b/application/src/main/kotlin/com/baidaidai/rootless_store/application/plugin/InstallPluginUseCase.kt index 2418c64c..d63c6d79 100644 --- a/application/src/main/kotlin/com/baidaidai/rootless_store/application/plugin/InstallPluginUseCase.kt +++ b/application/src/main/kotlin/com/baidaidai/rootless_store/application/plugin/InstallPluginUseCase.kt @@ -1,6 +1,7 @@ package com.baidaidai.rootless_store.application.plugin import com.baidaidai.rootless_store.core.util.formatAsMultilineString +import com.baidaidai.rootless_store.data.fileSystem.gateway.AndroidFileSystemDefaultOperatorGatewayImpl import com.baidaidai.rootless_store.data.plugin.gateway.PluginGatewayImpl import com.baidaidai.rootless_store.data.plugin.repository.PluginRepositoryImpl import com.baidaidai.rootless_store.data.plugin.repository.PluginStatusRepositoryImpl @@ -15,11 +16,21 @@ import javax.inject.Inject import java.io.File class InstallPluginUseCase @Inject constructor( + private val androidFileSystemDefaultOperatorGatewayImpl: AndroidFileSystemDefaultOperatorGatewayImpl, private val pluginRepositoryImpl: PluginRepositoryImpl, private val pluginStatusRepositoryImpl: PluginStatusRepositoryImpl, private val pluginGatewayImpl: PluginGatewayImpl, private val installShellPluginUseCase: InstallShellPluginUseCase ) { + + suspend operator fun invoke(): Result { + val packageDirectory = androidFileSystemDefaultOperatorGatewayImpl + .getExternalAppPackageCacheDirectoryFile() + val localPackageFile = File(packageDirectory, "package.zip") + + return invoke(localPackageFile) + } + suspend operator fun invoke(packageFile: File): Result { val pluginManifest: PluginManifest = pluginGatewayImpl diff --git a/data/src/main/kotlin/com/baidaidai/rootless_store/data/signature/gateway/PackageSignatureGatewayImpl.kt b/data/src/main/kotlin/com/baidaidai/rootless_store/data/signature/gateway/PackageSignatureGatewayImpl.kt index 241ef97b..46f935e7 100644 --- a/data/src/main/kotlin/com/baidaidai/rootless_store/data/signature/gateway/PackageSignatureGatewayImpl.kt +++ b/data/src/main/kotlin/com/baidaidai/rootless_store/data/signature/gateway/PackageSignatureGatewayImpl.kt @@ -5,6 +5,7 @@ import android.util.Log import com.baidaidai.rootless_store.data.R import com.baidaidai.rootless_store.data.fileSystem.gateway.AndroidFileSystemReadOperatorGatewayImpl import com.baidaidai.rootless_store.data.signature.repository.CaCertificateRepositoryImpl +import com.baidaidai.rootless_store.domain.install.model.LocalPackageType import com.baidaidai.rootless_store.domain.install.model.PackageSignatureInfo import com.baidaidai.rootless_store.domain.install.model.PackageSignatureStatus import dagger.hilt.android.qualifiers.ApplicationContext @@ -88,7 +89,7 @@ class PackageSignatureGatewayImpl @Inject constructor( * * */ - private suspend fun hasTrustedContentSignature( + internal suspend fun hasTrustedContentSignature( packageBytes: ByteArray, signatureBytes: ByteArray ): Boolean { @@ -239,7 +240,7 @@ class PackageSignatureGatewayImpl @Inject constructor( * @since 2026.10.26 * @version 0.1.0 */ - private suspend fun loadTrustedCaCertificates(): Set { + internal suspend fun loadTrustedCaCertificates(): Set { Log.d(logTag, "Loading built-in and enabled custom CA certificates") val trustedCaCertificates = mutableSetOf() @@ -291,7 +292,7 @@ class PackageSignatureGatewayImpl @Inject constructor( } // 将 CMS 携带的中间证书链构建到任一已启用的 CA 信任锚。 - private fun hasTrustedCertificatePath( + internal fun hasTrustedCertificatePath( signerCertificate: X509Certificate, trustedCaCertificates: Set ): Boolean { @@ -338,6 +339,7 @@ class PackageSignatureGatewayImpl @Inject constructor( if (pluginManifest != null) { return PackageSignatureInfo( + packageType = LocalPackageType.Plugin, displayName = pluginManifest.pluginRenderingName, author = pluginManifest.author, validSigner = signatureStatus == PackageSignatureStatus.Trusted, @@ -354,6 +356,7 @@ class PackageSignatureGatewayImpl @Inject constructor( ?: error("Signed package did not contain a supported Plugin or Environment manifest") return PackageSignatureInfo( + packageType = LocalPackageType.Environment, displayName = environmentManifest.environmentRenderingName, author = environmentManifest.author, validSigner = signatureStatus == PackageSignatureStatus.Trusted, diff --git a/domain/src/main/kotlin/com/baidaidai/rootless_store/domain/install/model/PackageSignatureInfo.kt b/domain/src/main/kotlin/com/baidaidai/rootless_store/domain/install/model/PackageSignatureInfo.kt index 1763f920..76ecc009 100644 --- a/domain/src/main/kotlin/com/baidaidai/rootless_store/domain/install/model/PackageSignatureInfo.kt +++ b/domain/src/main/kotlin/com/baidaidai/rootless_store/domain/install/model/PackageSignatureInfo.kt @@ -1,6 +1,7 @@ package com.baidaidai.rootless_store.domain.install.model data class PackageSignatureInfo( + val packageType: LocalPackageType, val displayName: String, val author: String, val validSigner: Boolean = false, From 91af439d0a0fab14f5798d78f72e6a276266e237 Mon Sep 17 00:00:00 2001 From: "Creater. Bai" <1302064396@qq.com> Date: Mon, 26 Oct 2026 20:16:33 +0800 Subject: [PATCH 2/5] chore(ui): Complete package confirmation preview data Populate the required package type in the confirmation sheet preview. --- .../ui/components/pluginScreen/PluginBottomSheetContent.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ui/src/main/kotlin/com/baidaidai/rootless_store/ui/components/pluginScreen/PluginBottomSheetContent.kt b/ui/src/main/kotlin/com/baidaidai/rootless_store/ui/components/pluginScreen/PluginBottomSheetContent.kt index 48ebe970..262e48b3 100644 --- a/ui/src/main/kotlin/com/baidaidai/rootless_store/ui/components/pluginScreen/PluginBottomSheetContent.kt +++ b/ui/src/main/kotlin/com/baidaidai/rootless_store/ui/components/pluginScreen/PluginBottomSheetContent.kt @@ -30,6 +30,7 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.unit.dp import androidx.core.net.toUri +import com.baidaidai.rootless_store.domain.install.model.LocalPackageType import com.baidaidai.rootless_store.domain.install.model.PackageSignatureInfo import com.baidaidai.rootless_store.ui.R import com.baidaidai.rootless_store.ui.theme.ColorFactory @@ -263,6 +264,7 @@ fun PluginBottomSheetContent( private fun _preview_() { PluginBottomSheetContent( packageSignatureInfo = PackageSignatureInfo( + packageType = LocalPackageType.Plugin, displayName = "Demo Plugin", author = "Rootless Store", validSigner = false, From f03d149f8b7090b738f9dbb4036d88bea16ebe89 Mon Sep 17 00:00:00 2001 From: "Creater. Bai" <1302064396@qq.com> Date: Mon, 26 Oct 2026 20:16:42 +0800 Subject: [PATCH 3/5] docs(signature): Document certificate path validation Document the PKIX certificate-path helper and remove outdated verification comments. --- .../gateway/PackageSignatureGatewayImpl.kt | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/data/src/main/kotlin/com/baidaidai/rootless_store/data/signature/gateway/PackageSignatureGatewayImpl.kt b/data/src/main/kotlin/com/baidaidai/rootless_store/data/signature/gateway/PackageSignatureGatewayImpl.kt index 46f935e7..0b2e2a60 100644 --- a/data/src/main/kotlin/com/baidaidai/rootless_store/data/signature/gateway/PackageSignatureGatewayImpl.kt +++ b/data/src/main/kotlin/com/baidaidai/rootless_store/data/signature/gateway/PackageSignatureGatewayImpl.kt @@ -85,9 +85,6 @@ class PackageSignatureGatewayImpl @Inject constructor( * * @since 2026.10.26 * @version 0.1.0 - * - * - * */ internal suspend fun hasTrustedContentSignature( packageBytes: ByteArray, @@ -171,8 +168,6 @@ class PackageSignatureGatewayImpl @Inject constructor( * 那就直接跳出去吧 * * 这一步只浅筛,是否有有效签名者,不进行内容计算 - * - * Holder只是指针引用,保存了 Cert 的原始记录 */ val signerCertificateHolder = cmsCertificateList .firstOrNull { certificateHolder -> @@ -186,7 +181,6 @@ class PackageSignatureGatewayImpl @Inject constructor( signerCertificateHolder ) - // 签名者必须是有效期内的非 CA 证书,并允许数字签名用途。 signerCertificate.checkValidity() // 验证 CMS 内容签名 @@ -291,7 +285,18 @@ class PackageSignatureGatewayImpl @Inject constructor( } - // 将 CMS 携带的中间证书链构建到任一已启用的 CA 信任锚。 + /** + * 检查签名者证书能否构建到受信任的 CA 锚点。 + * + * 使用 Bouncy Castle 的 PKIX CertPathBuilder,以签名者证书作为目标, + * 尝试从提供的信任锚集合中构建证书路径;此处不执行证书吊销检查。 + * + * @param signerCertificate CMS 签名者证书 + * @param trustedCaCertificates 可作为信任锚的 CA 证书集合 + * @return 成功构建证书路径时为 true,否则为 false + * @since 2026.10.26 + * @version 0.1.0 + */ internal fun hasTrustedCertificatePath( signerCertificate: X509Certificate, trustedCaCertificates: Set From 9ae6e682f48f2f5b32f766ffe1edd8564c346b37 Mon Sep 17 00:00:00 2001 From: "Creater. Bai" <1302064396@qq.com> Date: Mon, 26 Oct 2026 20:16:51 +0800 Subject: [PATCH 4/5] refactor(ui): Reuse VerticalSpacer in package confirmation sheet Replace inline spacers with the shared VerticalSpacer and adjust the confirmation sheet's vertical spacing. --- .../components/pluginScreen/PluginBottomSheetContent.kt | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/ui/src/main/kotlin/com/baidaidai/rootless_store/ui/components/pluginScreen/PluginBottomSheetContent.kt b/ui/src/main/kotlin/com/baidaidai/rootless_store/ui/components/pluginScreen/PluginBottomSheetContent.kt index 262e48b3..d75f1a63 100644 --- a/ui/src/main/kotlin/com/baidaidai/rootless_store/ui/components/pluginScreen/PluginBottomSheetContent.kt +++ b/ui/src/main/kotlin/com/baidaidai/rootless_store/ui/components/pluginScreen/PluginBottomSheetContent.kt @@ -33,6 +33,7 @@ import androidx.core.net.toUri import com.baidaidai.rootless_store.domain.install.model.LocalPackageType import com.baidaidai.rootless_store.domain.install.model.PackageSignatureInfo import com.baidaidai.rootless_store.ui.R +import com.baidaidai.rootless_store.ui.components.common.VerticalSpacer import com.baidaidai.rootless_store.ui.theme.ColorFactory import com.baidaidai.rootless_store.ui.theme.SuccessColorSet import com.baidaidai.rootless_store.ui.theme.WarningColorSet @@ -198,9 +199,7 @@ fun PluginBottomSheetContent( ) } - androidx.compose.foundation.layout.Spacer( - modifier = Modifier.height(12.dp) - ) + VerticalSpacer(20.dp) Text( text = packageDescription, @@ -210,9 +209,7 @@ fun PluginBottomSheetContent( fontWeight = FontWeight(440) ) - androidx.compose.foundation.layout.Spacer( - modifier = Modifier.height(20.dp) - ) + VerticalSpacer(20.dp) Row( horizontalArrangement = Arrangement.spacedBy(12.dp), From 25027009bb4e41469111874f690e03471a9240e5 Mon Sep 17 00:00:00 2001 From: "Creater. Bai" <1302064396@qq.com> Date: Mon, 26 Oct 2026 20:17:00 +0800 Subject: [PATCH 5/5] feat(viewmodel): Route confirmed package installation by type Use signature metadata to invoke the matching plugin or environment installation use case. --- .../model/RootlessStorePluginScreenViewModel.kt | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/ui/src/main/kotlin/com/baidaidai/rootless_store/ui/model/RootlessStorePluginScreenViewModel.kt b/ui/src/main/kotlin/com/baidaidai/rootless_store/ui/model/RootlessStorePluginScreenViewModel.kt index 3f87fb8c..3eb401f1 100644 --- a/ui/src/main/kotlin/com/baidaidai/rootless_store/ui/model/RootlessStorePluginScreenViewModel.kt +++ b/ui/src/main/kotlin/com/baidaidai/rootless_store/ui/model/RootlessStorePluginScreenViewModel.kt @@ -19,11 +19,14 @@ import com.baidaidai.rootless_store.application.environment.ObserveEnvironmentsU import com.baidaidai.rootless_store.application.environment.ObserveEnvironmentStatusUseCase import com.baidaidai.rootless_store.application.environment.DisableEnvironmentUseCase import com.baidaidai.rootless_store.application.environment.EnableEnvironmentUseCase +import com.baidaidai.rootless_store.application.environment.InstallEnvironmentUseCase import com.baidaidai.rootless_store.application.plugin.DisablePluginUseCase import com.baidaidai.rootless_store.application.plugin.EnablePluginUseCase import com.baidaidai.rootless_store.application.environment.UninstallEnvironmentUseCase +import com.baidaidai.rootless_store.application.plugin.InstallPluginUseCase import com.baidaidai.rootless_store.application.plugin.ResolvePluginShareUriUseCase import com.baidaidai.rootless_store.application.plugin.ResolvePluginWebUiUriUseCase +import com.baidaidai.rootless_store.domain.install.model.LocalPackageType import com.baidaidai.rootless_store.domain.plugin.manifest.PluginManifest import com.baidaidai.rootless_store.domain.plugin.model.PluginStatus import com.baidaidai.rootless_store.ui.uistate.RootlessStorePluginScreenUiState @@ -49,6 +52,8 @@ class RootlessStorePluginScreenViewModel @Inject constructor( private val observeEnvironmentStatusUseCase: ObserveEnvironmentStatusUseCase, private val observePluginStatusUseCase: ObservePluginStatusUseCase, private val installLocalPackageUseCase: InstallLocalPackageUseCase, + private val installPluginUseCase: InstallPluginUseCase, + private val installEnvironmentUseCase: InstallEnvironmentUseCase, private val enablePluginUseCase: EnablePluginUseCase, private val disablePluginUseCase: DisablePluginUseCase, private val enableEnvironmentUseCase: EnableEnvironmentUseCase, @@ -132,9 +137,17 @@ class RootlessStorePluginScreenViewModel @Inject constructor( } fun confirmLocalPackageInstall() { + val packageSignatureInfo = _pluginScreenUiState.value.packageSignatureInfo clearLocalPackageInstallConfirmation() - installLocalPackage() + + viewModelScope.launch { + when (packageSignatureInfo!!.packageType) { + LocalPackageType.Plugin -> installPluginUseCase() + else -> installEnvironmentUseCase() + } + } } + fun dismissLocalPackageInstallConfirmation() { clearLocalPackageInstallConfirmation() viewModelScope.launch {