feat: establish native desktop update trust root

This commit is contained in:
冰朔 2026-08-12 10:29:00 +08:00
commit cff488bb03
29 changed files with 8485 additions and 60 deletions

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,26 @@
[package]
name = "hololake-native-desktop"
version = "0.1.0"
description = "HoloLake native desktop foundation"
authors = ["HoloLake"]
license = "AGPL-3.0-or-later"
edition = "2021"
rust-version = "1.77.2"
[lib]
name = "hololake_native_desktop_lib"
crate-type = ["staticlib", "cdylib", "rlib"]
[build-dependencies]
tauri-build = { version = "2.5.4", features = [] }
[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tauri = { version = "=2.10.2", features = [] }
tauri-runtime = "=2.10.0"
tauri-runtime-wry = "=2.10.0"
tauri-plugin-updater = "2.10.0"
[dev-dependencies]
tempfile = "3"

View file

@ -0,0 +1,3 @@
fn main() {
tauri_build::build()
}

View file

@ -0,0 +1,7 @@
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "Minimum HoloLake desktop foundation; updater actions are not exposed to the webview yet.",
"windows": ["main"],
"permissions": ["core:default"]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

View file

@ -0,0 +1,11 @@
{
"schema": "hololake.release-trust/v1",
"state": "UNPROVISIONED_FAIL_CLOSED",
"endpoints": [],
"publicKey": "",
"allowedReleaseHosts": [],
"automaticCheckOnStartup": false,
"automaticDownload": false,
"humanOptInInstallRequired": true,
"automaticRestart": false
}

View file

@ -0,0 +1,12 @@
mod release_trust;
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.setup(|app| {
release_trust::install_updater_if_provisioned(app.handle())?;
Ok(())
})
.run(tauri::generate_context!())
.expect("failed to run HoloLake native desktop");
}

View file

@ -0,0 +1,5 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
fn main() {
hololake_native_desktop_lib::run();
}

View file

@ -0,0 +1,179 @@
use serde::Deserialize;
use tauri::{AppHandle, Runtime, Url};
use tauri_plugin_updater::UpdaterExt;
const EMBEDDED_RELEASE_TRUST: &str = include_str!("../release-trust.json");
#[derive(Debug, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
struct ReleaseTrust {
schema: String,
state: TrustState,
endpoints: Vec<String>,
public_key: String,
allowed_release_hosts: Vec<String>,
automatic_check_on_startup: bool,
automatic_download: bool,
human_opt_in_install_required: bool,
automatic_restart: bool,
}
#[derive(Debug, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
enum TrustState {
UnprovisionedFailClosed,
Provisioned,
}
enum ValidatedReleaseTrust {
Disabled,
Enabled {
endpoints: Vec<Url>,
public_key: String,
},
}
fn validate_release_trust(raw: &str) -> Result<ValidatedReleaseTrust, String> {
let trust: ReleaseTrust = serde_json::from_str(raw)
.map_err(|error| format!("invalid HoloLake release trust document: {error}"))?;
if trust.schema != "hololake.release-trust/v1" {
return Err("unsupported HoloLake release trust schema".into());
}
if trust.automatic_check_on_startup || trust.automatic_download || trust.automatic_restart {
return Err("automatic check, download and restart must remain disabled".into());
}
if !trust.human_opt_in_install_required {
return Err("human opt-in installation is required".into());
}
match trust.state {
TrustState::UnprovisionedFailClosed => {
if !trust.endpoints.is_empty()
|| !trust.public_key.is_empty()
|| !trust.allowed_release_hosts.is_empty()
{
return Err(
"unprovisioned trust must not contain endpoint or public key material".into(),
);
}
Ok(ValidatedReleaseTrust::Disabled)
}
TrustState::Provisioned => {
if trust.endpoints.len() != 1 {
return Err("exactly one HoloLake release endpoint is required".into());
}
if trust.public_key.trim().is_empty() {
return Err("the HoloLake updater public key is required".into());
}
if trust.allowed_release_hosts.len() != 1 {
return Err("exactly one HoloLake-owned release host is required".into());
}
let allowed_host = trust.allowed_release_hosts[0].trim();
if allowed_host.is_empty() || allowed_host.contains('/') || allowed_host.contains(':') {
return Err("the HoloLake release host must be a bare DNS name".into());
}
let endpoint = Url::parse(&trust.endpoints[0])
.map_err(|error| format!("invalid HoloLake release endpoint: {error}"))?;
if endpoint.scheme() != "https" {
return Err("the HoloLake release endpoint must use HTTPS".into());
}
if endpoint.host_str() != Some(allowed_host) {
return Err(
"the updater endpoint is not owned by the configured HoloLake host".into(),
);
}
Ok(ValidatedReleaseTrust::Enabled {
endpoints: vec![endpoint],
public_key: trust.public_key,
})
}
}
}
pub fn install_updater_if_provisioned<R: Runtime>(
app: &AppHandle<R>,
) -> Result<(), Box<dyn std::error::Error>> {
match validate_release_trust(EMBEDDED_RELEASE_TRUST)? {
ValidatedReleaseTrust::Disabled => Ok(()),
ValidatedReleaseTrust::Enabled {
endpoints,
public_key,
} => {
let updater = tauri_plugin_updater::Builder::new()
.pubkey(public_key)
.build();
app.plugin(updater)?;
app.updater_builder().endpoints(endpoints)?.build()?;
Ok(())
}
}
}
#[cfg(test)]
mod tests {
use super::{validate_release_trust, ValidatedReleaseTrust};
fn document(state: &str, endpoint: &str, public_key: &str, allowed_host: &str) -> String {
format!(
r#"{{
"schema":"hololake.release-trust/v1",
"state":"{state}",
"endpoints":[{endpoint}],
"publicKey":"{public_key}",
"allowedReleaseHosts":[{allowed_host}],
"automaticCheckOnStartup":false,
"automaticDownload":false,
"humanOptInInstallRequired":true,
"automaticRestart":false
}}"#
)
}
#[test]
fn unprovisioned_document_disables_update_networking() {
let trust =
validate_release_trust(&document("UNPROVISIONED_FAIL_CLOSED", "", "", "")).unwrap();
assert!(matches!(trust, ValidatedReleaseTrust::Disabled));
}
#[test]
fn accepts_https_shape_but_rejects_insecure_endpoints() {
let owned_shape = document(
"PROVISIONED",
"\"https://releases.example.test/latest.json\"",
"public",
"\"releases.example.test\"",
);
assert!(
validate_release_trust(&owned_shape).is_ok(),
"endpoint ownership is additionally checked by release provisioning"
);
let insecure = document(
"PROVISIONED",
"\"http://releases.example.test/latest.json\"",
"public",
"\"releases.example.test\"",
);
assert!(validate_release_trust(&insecure).is_err());
let mismatched_host = document(
"PROVISIONED",
"\"https://upstream.example.test/latest.json\"",
"public",
"\"releases.example.test\"",
);
assert!(validate_release_trust(&mismatched_host).is_err());
}
#[test]
fn rejects_automatic_or_nonconsensual_install_policy() {
let automatic = document("UNPROVISIONED_FAIL_CLOSED", "", "", "")
.replace("\"automaticDownload\":false", "\"automaticDownload\":true");
assert!(validate_release_trust(&automatic).is_err());
let no_opt_in = document("UNPROVISIONED_FAIL_CLOSED", "", "", "").replace(
"\"humanOptInInstallRequired\":true",
"\"humanOptInInstallRequired\":false",
);
assert!(validate_release_trust(&no_opt_in).is_err());
}
}

View file

@ -0,0 +1,33 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "HoloLake",
"version": "0.1.0",
"identifier": "world.guanghu.hololake",
"build": {
"frontendDist": "../dist",
"devUrl": "http://127.0.0.1:5210",
"beforeDevCommand": "npm run dev",
"beforeBuildCommand": "npm run build"
},
"app": {
"windows": [
{
"label": "main",
"title": "HoloLake",
"width": 1180,
"height": 760,
"minWidth": 760,
"minHeight": 520
}
],
"security": {
"csp": "default-src 'self'; connect-src 'self' ipc: http://ipc.localhost; img-src 'self' data:; style-src 'self' 'unsafe-inline'; script-src 'self'"
}
},
"bundle": {
"active": true,
"targets": "all",
"createUpdaterArtifacts": true,
"category": "Productivity"
}
}