use proxywarden_lib::component_catalog::ComponentId; use proxywarden_lib::component_packages::{ ComponentPackageService, ComponentPackagesError, GithubReleaseDigestProof, NativePrivilegedBundleVerifier, PackageSignatureVerifier, PackageSource, PrivilegedBundleVerificationError, PrivilegedBundleVerifier, PrivilegedCachedUpdatePlan, SignaturePublisher, SignatureVerifierError, UpdateRequestKind, UpdateTransport, UpdateTransportError, UpdateTransportRequest, UpdateTransportResponse, }; use proxywarden_lib::safe_fs::protect_path_for_owner_admin_system; use proxywarden_lib::storage::StoragePaths; use serde_json::json; use sha2::{Digest, Sha256}; use std::fs; use std::io::Cursor; use std::path::{Path, PathBuf}; use std::sync::atomic::{AtomicUsize, Ordering}; use uuid::Uuid; #[test] fn privileged_fresh_install_ignores_forged_newer_cache_and_has_no_transport() { let workspace = TestWorkspace::new(); let paths = workspace.storage_paths(); let forged = paths .packages_dir .join(ComponentId::SingBox.as_str()) .join("999.0.0"); fs::create_dir_all(&forged).expect("create forged cache"); fs::write(forged.join("forged.zip"), b"not an official package").expect("write forged cache"); let service = ComponentPackageService::open(bundled_root(), &paths).expect("open component packages"); let verifier = ExplicitTestBundleVerifier::default(); let lease = service .lease_bundled_for_privileged_install(ComponentId::SingBox, &verifier) .expect("lease immutable bundled sing-box"); assert_eq!(lease.proof().source, PackageSource::Bundled); assert_eq!(lease.proof().version, "1.13.19"); assert_eq!(lease.proof().independent_proof, None); assert_eq!(verifier.calls.load(Ordering::SeqCst), 1); assert!(lease.asset_path().starts_with(bundled_root())); // There is intentionally no transport argument on the fresh-install API. } #[test] fn native_bundle_verifier_rejects_user_owned_or_noncanonical_bundle() { let workspace = TestWorkspace::new(); let service = ComponentPackageService::open(bundled_root(), &workspace.storage_paths()) .expect("open component packages"); assert!(matches!( service.lease_bundled_for_privileged_install( ComponentId::SingBox, &NativePrivilegedBundleVerifier ), Err(ComponentPackagesError::UntrustedBundleRoot) )); } #[test] fn privileged_update_requires_the_exact_live_latest_proof_before_staging() { let workspace = TestWorkspace::new(); let service = ComponentPackageService::open(bundled_root(), &workspace.storage_paths()) .expect("open component packages"); let plan = sing_box_plan(b"cached sing-box update"); let mut wrong_latest = plan.clone(); wrong_latest.independent_proof.release_id += 1; let transport = MetadataTransport::success(metadata_for(&wrong_latest)); assert!(matches!( service.lease_cached_update_for_privileged_install( &plan, workspace.path(), &transport, &NoopVerifier, ), Err(ComponentPackagesError::InvalidPrivilegedUpdatePlan) )); assert_eq!(transport.calls.load(Ordering::SeqCst), 1); assert_no_privileged_staging(workspace.path()); let timeout = MetadataTransport::failure(); assert!(matches!( service.lease_cached_update_for_privileged_install( &plan, workspace.path(), &timeout, &NoopVerifier, ), Err(ComponentPackagesError::Transport) )); assert_eq!(timeout.calls.load(Ordering::SeqCst), 1); assert_no_privileged_staging(workspace.path()); } #[cfg(windows)] #[test] fn tampered_cache_fails_before_any_admin_staging_is_created() { let workspace = TestWorkspace::new(); let paths = workspace.storage_paths(); let service = ComponentPackageService::open(bundled_root(), &paths).expect("open component packages"); let plan = sing_box_plan(b"official update bytes"); write_cache(&paths, &plan, b"tampered cache bytes"); let transport = MetadataTransport::success(metadata_for(&plan)); assert!(matches!( service.lease_cached_update_for_privileged_install( &plan, workspace.path(), &transport, &NoopVerifier, ), Err(ComponentPackagesError::DigestMismatch) )); assert_eq!(transport.calls.load(Ordering::SeqCst), 1); assert_no_privileged_staging(workspace.path()); } #[test] fn winsw_and_vc_runtime_make_zero_privileged_update_transport_calls() { let workspace = TestWorkspace::new(); let service = ComponentPackageService::open(bundled_root(), &workspace.storage_paths()) .expect("open component packages"); for component_id in [ComponentId::Winsw, ComponentId::VcRuntime] { let transport = MetadataTransport::failure(); let mut plan = sing_box_plan(b"unused"); plan.component_id = component_id; assert!(matches!( service.lease_cached_update_for_privileged_install( &plan, workspace.path(), &transport, &NoopVerifier, ), Err(ComponentPackagesError::NoTrustedUpdate) )); assert_eq!(transport.calls.load(Ordering::SeqCst), 0); } } #[derive(Default)] struct ExplicitTestBundleVerifier { calls: AtomicUsize, } struct NoopVerifier; impl PackageSignatureVerifier for NoopVerifier { fn verify(&self, _path: &Path) -> Result { Err(SignatureVerifierError) } } impl PrivilegedBundleVerifier for ExplicitTestBundleVerifier { fn verify( &self, bundled_root: &Path, catalog_path: &Path, asset_path: &Path, ) -> Result<(), PrivilegedBundleVerificationError> { self.calls.fetch_add(1, Ordering::SeqCst); if catalog_path != bundled_root.join("catalog.json") || !asset_path.starts_with(bundled_root) { return Err(PrivilegedBundleVerificationError); } Ok(()) } } struct MetadataTransport { response: Result, UpdateTransportError>, calls: AtomicUsize, } impl MetadataTransport { fn success(response: Vec) -> Self { Self { response: Ok(response), calls: AtomicUsize::new(0), } } fn failure() -> Self { Self { response: Err(UpdateTransportError::RequestFailed), calls: AtomicUsize::new(0), } } } impl UpdateTransport for MetadataTransport { fn get( &self, request: &UpdateTransportRequest, ) -> Result { self.calls.fetch_add(1, Ordering::SeqCst); assert_eq!(request.kind, UpdateRequestKind::GithubReleaseMetadata); assert_eq!( request.url, "https://api.github.com/repos/SagerNet/sing-box/releases/latest" ); let body = self.response.clone()?; Ok(UpdateTransportResponse { status: 200, location: None, content_length: Some(body.len() as u64), body: Box::new(Cursor::new(body)), }) } } fn sing_box_plan(bytes: &[u8]) -> PrivilegedCachedUpdatePlan { let version = "1.14.0"; let asset_name = format!("sing-box-{version}-windows-amd64.zip"); let sha256 = format!("{:x}", Sha256::digest(bytes)); PrivilegedCachedUpdatePlan { component_id: ComponentId::SingBox, version: version.to_string(), independent_proof: GithubReleaseDigestProof { repository: "SagerNet/sing-box".to_string(), release_id: 700, asset_id: 701, stable_tag: format!("v{version}"), asset_name, size: bytes.len() as u64, sha256_from_api: sha256, verified_signatures: Vec::new(), }, } } fn metadata_for(plan: &PrivilegedCachedUpdatePlan) -> Vec { let proof = &plan.independent_proof; serde_json::to_vec(&json!({ "id": proof.release_id, "tag_name": proof.stable_tag, "draft": false, "prerelease": false, "assets": [{ "id": proof.asset_id, "name": proof.asset_name, "size": proof.size, "digest": format!("sha256:{}", proof.sha256_from_api), "browser_download_url": format!( "https://github.com/{}/releases/download/{}/{}", proof.repository, proof.stable_tag, proof.asset_name ) }] })) .expect("serialize GitHub metadata") } #[cfg(windows)] fn write_cache(paths: &StoragePaths, plan: &PrivilegedCachedUpdatePlan, bytes: &[u8]) { use proxywarden_lib::component_packages::{ PackageCacheManifest, PACKAGE_CACHE_MANIFEST_FILENAME, PACKAGE_CACHE_MANIFEST_SCHEMA_VERSION, }; let component_root = paths.packages_dir.join(plan.component_id.as_str()); let version_root = component_root.join(&plan.version); fs::create_dir_all(&version_root).expect("create cache directory"); let asset_path = version_root.join(&plan.independent_proof.asset_name); fs::write(&asset_path, bytes).expect("write cache bytes"); let manifest_path = version_root.join(PACKAGE_CACHE_MANIFEST_FILENAME); let manifest = PackageCacheManifest { schema_version: PACKAGE_CACHE_MANIFEST_SCHEMA_VERSION, component_id: plan.component_id, version: plan.version.clone(), asset_name: plan.independent_proof.asset_name.clone(), sha256: plan.independent_proof.sha256_from_api.clone(), size: plan.independent_proof.size, independent_proof: plan.independent_proof.clone(), }; fs::write( &manifest_path, serde_json::to_vec_pretty(&manifest).expect("serialize cache manifest"), ) .expect("write cache manifest"); for path in [ paths.packages_dir.as_path(), component_root.as_path(), version_root.as_path(), manifest_path.as_path(), asset_path.as_path(), ] { protect_path_for_owner_admin_system(path).expect("protect cache path"); } } fn assert_no_privileged_staging(parent: &Path) { let staging = fs::read_dir(parent) .expect("read staging parent") .filter_map(Result::ok) .filter_map(|entry| entry.file_name().into_string().ok()) .filter(|name| name.starts_with(".package-")) .collect::>(); assert!( staging.is_empty(), "unexpected staging entries: {staging:?}" ); } fn bundled_root() -> PathBuf { Path::new(env!("CARGO_MANIFEST_DIR")) .join("bundled") .join("components") } struct TestWorkspace { path: PathBuf, } impl TestWorkspace { fn new() -> Self { let target = Path::new(env!("CARGO_MANIFEST_DIR")).join("target"); fs::create_dir_all(&target).expect("create Cargo target directory"); let path = target.join(format!("privileged-package-trust-{}", Uuid::new_v4())); fs::create_dir(&path).expect("create test workspace"); Self { path } } fn path(&self) -> &Path { &self.path } fn storage_paths(&self) -> StoragePaths { StoragePaths::new(&self.path) } } impl Drop for TestWorkspace { fn drop(&mut self) { if self .path .file_name() .and_then(|name| name.to_str()) .is_some_and(|name| name.starts_with("privileged-package-trust-")) { let _ = fs::remove_dir_all(&self.path); } } }