release: formalize HoloLake 0.5.0 numbered root

This commit is contained in:
冰朔 2026-08-18 23:37:32 +08:00
commit 40b8c49324
10 changed files with 401 additions and 6 deletions

View file

@ -0,0 +1,66 @@
{
"schema": "hololake.installed-numbered-root-acceptance/v1",
"record_id": "HLP-HOLOLAKE-0.5.0-NUMBERED-ROOT-INSTALLED-20260818",
"state": "LOCAL_DEVELOPER_ID_SIGNED_INSTALLED_ACCEPTANCE_PASS_PUBLIC_NOTARIZATION_PENDING",
"observed_at": "2026-08-18T15:18:00Z",
"source": {
"branch": "codex/hololake-clean-reassembly-20260818",
"source_commit": "5d01607459043d0713bc562ef6d04dec198930a9",
"numbered_root_commit": "64abf969bfbc1c576d4fa84ae3282d32efbfcc38"
},
"installed_application": {
"path": "/Users/bingshuolingdianyuanhe/Desktop/HoloLake.app",
"version": "0.5.0",
"bundle_identifier": "world.guanghu.hololake",
"architecture": "arm64",
"binary_sha256": "2e162ff077187310f816f58df2250e3ee939eb489fe3c7b00e94557ff8f3370d",
"process_path_verified": true
},
"developer_id": {
"identity": "Developer ID Application: bei sun (825A9L3G7Q)",
"team_identifier": "825A9L3G7Q",
"cdhash": "7a2372ed85fc3b775e9a352217b123a4bdee0d64",
"strict_signature_verification": "PASS",
"designated_requirement": "PASS",
"gatekeeper": "REJECTED_UNNOTARIZED_DEVELOPER_ID",
"apple_notarization_and_stapling": "PENDING"
},
"runtime": {
"real_webview_loaded": true,
"visible_version": "V0.5.0",
"visible_domain": "第五域 · 光湖本源域",
"entered_surface": "永恒湖心系统",
"numbered_ipc_receipt_rows": 50,
"grant_and_execution_rows_present": true,
"empty_authority_binding_digest_rows": 0,
"recalculated_receipt_chain_failures": 0,
"last_sequence": 50,
"last_receipt_hash": "a294463ad9e6959d0f12ce5c2b59e11d97581e067b9480df9ae598dfafc067df"
},
"old_application": {
"version": "0.4.1",
"role": "READ_ONLY_PRE_NUMBERED_ROOT_MODULE_DONOR",
"archive_receipt": "/Volumes/JZAO/HoloLake/artifacts/hololake-release/0.4.1/macos-arm64/pre-numbered-root-donor/archive-receipt.json",
"repair_in_place": false
},
"release_boundary": {
"local_signed_install_complete": true,
"public_signed_notarized_release_complete": false,
"updater_public_key_continuity": "PASS_EXISTING_0.4.1_TRUST_ROOT_REUSED",
"updater_private_key_available_to_current_pipeline": true,
"updater_signature_generated_and_verified": true,
"pre_notarization_updater_artifact": {
"path": "src-tauri/target/release/bundle/macos/HoloLake.app.tar.gz",
"sha256": "4176bb7ea83c820744239c228671840289f87675329182bc055938e03bba9693",
"signature_path": "src-tauri/target/release/bundle/macos/HoloLake.app.tar.gz.sig",
"signature_sha256": "ae02c2918c26b6e3cc2389fec8d9f98884ae639d448edc78a4ec80ee30a8ae92",
"publication_allowed": false
},
"apple_notarization_credentials_available_to_current_pipeline": false,
"public_broadcast_activated": false
},
"persona_boundary": {
"current_codex_carrier_binding_claimed": false,
"runtime_persona_binding_created_by_numbered_ipc": false
}
}

View file

@ -0,0 +1,176 @@
{
"schema": "hololake.module-donor-admission-registry/v1",
"record_id": "HLP-MODULE-DONOR-ADMISSION-001",
"state": "DONORS_QUARANTINED_CANDIDATES_NUMBERED_NOT_ADMITTED",
"root_rule": {
"official_base": "HOLOLAKE_0.5.0_NUMBERED_IPC_ROOT",
"repair_old_application_in_place": false,
"bulk_merge_or_wholesale_copy_allowed": false,
"one_candidate_per_admission_cycle": true,
"candidate_number_is_runtime_module_number": false,
"permanent_module_number_assignment_before_acceptance": false,
"all_frontend_backend_calls_must_cross_numbered_ipc": true,
"shared_file_merge_is_admission_evidence": false
},
"donors": [
{
"donor_id": "HLP-DONOR-COMPILED-DESKTOP-0.4.1",
"kind": "COMPILED_MACOS_APPLICATION",
"path": "/Volumes/JZAO/HoloLake/artifacts/hololake-release/0.4.1/macos-arm64/pre-numbered-root-donor/HoloLake.app",
"state": "READ_ONLY",
"source_commit": null,
"source_commit_state": "UNKNOWN_NOT_INFERRED_FROM_COMPILED_BUNDLE",
"binary_sha256": "adc0d8b028a8b874c39909265ed7c41e7c24e4fe5b38adba04e8f72b64900c15",
"use": "BEHAVIOR_AND_VISIBLE_PRODUCT_REFERENCE_ONLY"
},
{
"donor_id": "HLP-DONOR-CHAOTIC-WORKTREE-20260818",
"kind": "DIRTY_SOURCE_WORKTREE",
"path": "/Users/bingshuolingdianyuanhe/Documents/Codex/2026-08-15/new-chat-2/work/jd-guanghu-supervisor/product-source/hololake-native-desktop",
"observed_head": "a8fe571b5d5c0a45207b64ac538d729b3d719d21",
"observed_dirty_path_count": 30,
"state": "READ_ONLY_UNTRUSTED_AS_A_WHOLE",
"use": "INDIVIDUAL_MODULE_SOURCE_CANDIDATES_ONLY"
}
],
"rejected_inputs": [
{
"candidate_number": "HLP-DONOR-CAND-0000",
"name": "legacy_numbered_operation_runtime",
"state": "REJECTED_SUPERSEDED",
"why": "It predates the single numbered IPC root and must not become a second numbering authority.",
"paths": [
"contracts/numbered-operation-runtime.json",
"src/modules/numbered-runtime.ts",
"src-tauri/src/numbered_operation_runtime.rs"
]
},
{
"candidate_number": "HLP-DONOR-CAND-SHARED-MUTATIONS",
"name": "shared_file_mutation_set",
"state": "REJECTED_AS_MERGE_UNIT",
"why": "These files mix unrelated modules and divergent frontend/backend routes; each needed behavior must be reconstructed behind its owning numbered module.",
"examples": [
"src/main.tsx",
"src/styles.css",
"src-tauri/src/lib.rs",
"src-tauri/src/knowledge_base.rs",
"src-tauri/src/gls_protocol_runtime.rs"
]
}
],
"candidates": [
{
"admission_order": 1,
"candidate_number": "HLP-DONOR-CAND-0001",
"name": "native_composition_module_runtime",
"state": "QUARANTINED_ASSESS_FIRST",
"paths": [
"contracts/native-composition-runtime.json",
"src/modules/native-composition",
"src-tauri/src/native_composition.rs"
],
"why_first": "A module needs an isolated mount, self-test, unmount and rollback boundary before content modules can be admitted safely."
},
{
"admission_order": 2,
"candidate_number": "HLP-DONOR-CAND-0002",
"name": "channel_document_and_spreadsheet_workbench",
"state": "QUARANTINED_PENDING_ADMISSION",
"paths": [
"src/modules/channel-workbench"
]
},
{
"admission_order": 3,
"candidate_number": "HLP-DONOR-CAND-0003",
"name": "persona_channel_body",
"state": "QUARANTINED_PENDING_ADMISSION",
"paths": [
"contracts/persona-channel-body.json",
"src/PersonaChannelBody.tsx",
"src-tauri/src/persona_channel_body.rs",
"src-tauri/src/channel_growth.rs"
],
"boundary": "UI_AND_PERSISTENCE_BODY_ONLY; DOES_NOT_CREATE_PERSONA_BINDING"
},
{
"admission_order": 4,
"candidate_number": "HLP-DONOR-CAND-0004",
"name": "education_workbench",
"state": "QUARANTINED_PENDING_ADMISSION",
"paths": [
"contracts/education-workspace.json",
"src/modules/education-adaptive-rendering.ts",
"src/modules/education-data.ts",
"src/modules/education-document-engine.tsx",
"src-tauri/src/education_translation.rs",
"src-tauri/src/education_workspace.rs"
]
},
{
"admission_order": 5,
"candidate_number": "HLP-DONOR-CAND-0005",
"name": "web_novel_workbench_and_author_modules",
"state": "QUARANTINED_PENDING_ADMISSION",
"paths": [
"contracts/web-novel-workspace.json",
"contracts/web-novel-module-marketplace-plan.json",
"src/WebNovelWorkspace.tsx",
"src/AuthorModuleCenter.tsx",
"src/AuthorWritingSidecar.tsx",
"src-tauri/src/web_novel_workspace.rs",
"src-tauri/src/web_novel_import.rs",
"src-tauri/src/web_novel_author.rs",
"src-tauri/src/web_novel_modules.rs"
]
},
{
"admission_order": 6,
"candidate_number": "HLP-DONOR-CAND-0006",
"name": "mobile_sync",
"state": "QUARANTINED_PENDING_ADMISSION",
"paths": [
"contracts/mobile-sync-v1.json",
"src/MobileSyncPanel.tsx",
"src-tauri/src/mobile_sync.rs"
]
},
{
"admission_order": 7,
"candidate_number": "HLP-DONOR-CAND-0007",
"name": "dynamic_language_world_visual_surface",
"state": "QUARANTINED_PENDING_ADMISSION",
"paths": [
"contracts/dynamic-language-world-visual-system.json",
"src/StarlakeSurface.tsx",
"src/starlake-surface.css",
"src/traditional-surface.tsx",
"src/traditional-surface.css",
"src-tauri/src/world_climate.rs"
]
}
],
"admission_gate": [
"EXTRACT_ONLY_DECLARED_CANDIDATE_PATHS",
"REVIEW_SOURCE_PROVENANCE_AND_LICENSE",
"DEFINE_DATA_PERMISSION_AND_RESOURCE_BOUNDARY",
"ALLOCATE_NUMBERED_IPC_MODULE_TARGET_AND_OPERATION_COORDINATES",
"IMPLEMENT_ADAPTER_WITHOUT_RAW_TAURI_INVOKE",
"ADD_UNIT_INTEGRATION_AND_NEGATIVE_ROUTE_TESTS",
"BUILD_FROM_CLEAN_OFFICIAL_BASE",
"INSTALL_IN_ISOLATED_RUNTIME",
"VERIFY_MOUNT_SELF_TEST_RESTART_UNMOUNT_AND_ROLLBACK",
"WRITE_HASH_CHAINED_RUNTIME_RECEIPT",
"ONLY_THEN_ASSIGN_PERMANENT_MODULE_NUMBER"
],
"hot_install_boundary": {
"source_repository_is_directly_executable": false,
"immutable_signed_artifact_required": true,
"compatibility_manifest_required": true,
"permissions_declared_before_mount": true,
"human_confirmation_required_when_boundary_expands": true,
"rollback_on_self_test_failure": true,
"user_data_survives_unmount": true
}
}

View file

@ -90,10 +90,16 @@ HoloLake 0.3.0 includes a live, read-only projection of the PNCC resident runtim
## Release pipeline
`npm run release:macos -- release/inputs/<version>.json` is the only product-owned macOS release entry. It fails before building unless the embedded trust contains the exact registered HoloLake HTTPS endpoint and updater public key, the immutable `v<version>` tag equals the clean `main` head, and the Developer ID, Tauri updater-signing and Apple notarization credential sets are supplied at runtime. The pipeline runs all product and Rust gates, creates updater artifacts through a temporary Tauri override, then requires strict code-signature verification, Gatekeeper acceptance and stapled Apple notarization before writing the HoloLake broadcast and receipts.
`npm run release:macos -- release/inputs/<version>.json` is the only product-owned macOS release entry. It fails before building unless the embedded trust contains the exact registered HoloLake HTTPS endpoint and updater public key, the immutable `v<version>` tag equals the clean `main` head, and the Developer ID, Tauri updater-signing and Apple notarization credential sets are supplied at runtime. A protected updater-key path is materialized only into the child build environment; the key is never printed or copied into source. The pipeline runs all product and Rust gates, creates updater artifacts through a temporary Tauri override, verifies the updater signature against the embedded product trust, then requires strict code-signature verification, Gatekeeper acceptance and stapled Apple notarization before writing the HoloLake broadcast and receipts.
Generated packages, private release inputs and credentials are not committed. The pipeline never uploads or activates a release; its terminal artifact is a bounded folder ready for a separately authorized JD-controller upload and server-owned readback receipt.
## Numbered-root module admission
HoloLake 0.5.0 is the clean numbered-root base. The compiled 0.4.1 desktop application and the divergent dirty source worktree are read-only donors, not merge bases. Their old numbered-operation runtime is explicitly superseded, and mutations to shared files such as `src/main.tsx` or `src-tauri/src/lib.rs` are never accepted as a unit.
Each donor capability receives a candidate coordinate, but no permanent runtime module number, until one isolated admission cycle has reviewed provenance and permissions, allocated numbered IPC module/target/operation coordinates, implemented an adapter without raw Tauri invoke, passed negative-route and data tests, and produced installed mount, restart, unmount and rollback receipts. The admission order and candidate inventory are recorded in `contracts/module-donor-admission-registry.json`.
## Stage-one convergence verdict
The Tauri source in this directory is the only future HoloLake desktop mainline. An installed build of it is an acceptance candidate, not a separate product line and not proof that stage one exists. The Electron 0.8.0 product and the legacy Tauri/platform sources remain read-only UX, behavior, engineering and protected-data donors until inventory, backup, readback, reversible migration rehearsal and signed installed-runtime acceptance all pass.

View file

@ -56,9 +56,11 @@
"local_runtime_acceptance_receipt": "audit/local-runtime-acceptance-20260815.json",
"domain_membrane_pncc_installed_acceptance_receipt": "audit/hololake-0.4.0-domain-membrane-pncc-installed-acceptance-20260816.json",
"updater_bootstrap_installed_acceptance_receipt": "audit/hololake-0.4.1-updater-bootstrap-installed-acceptance-20260817.json",
"installed_desktop_path": "/Applications/HoloLake.app",
"installed_desktop_path": "/Users/bingshuolingdianyuanhe/Desktop/HoloLake.app",
"old_desktop_apps_recoverably_archived": true,
"public_developer_id_and_notarized_acceptance": false,
"local_developer_id_signed_0_5_0_installed_acceptance": true,
"numbered_root_installed_acceptance_receipt": "audit/hololake-0.5.0-numbered-root-installed-acceptance-20260818.json",
"stage_one_implementation_order": [
"PERSONAL_CHANNEL_IDENTITY_TASK_EVENT_RECEIPT_KERNEL",
"KNOWLEDGE_TREE_PAGE_SEARCH_AND_LOCAL_PERSISTENCE",
@ -99,8 +101,11 @@
"automatic_restart_allowed": false,
"private_signing_material_allowed_in_source": false,
"donor_audit_record": "audit/donor-audit.json",
"donor_audit_complete": false,
"donor_audit_complete": true,
"donor_source_copying_allowed": false,
"module_donor_admission_registry": "contracts/module-donor-admission-registry.json",
"module_donor_bulk_merge_allowed": false,
"module_donor_one_candidate_per_admission_cycle": true,
"stage_one_product_contract": "contracts/stage-one-platform.json",
"local_development_bridge_contract": "contracts/local-development-bridge.json",
"account_single_writer_kernel_implemented": true,
@ -128,7 +133,7 @@
"pncc_jd_live_server_projection_repository_content_exposed": false,
"pncc_jd_live_server_projection_write_authority": false,
"pncc_jd_live_server_projection_carrier_state": "UNBOUND_EVIDENCE_REQUIRED",
"installed_local_product_version": "0.4.1",
"installed_local_product_version": "0.5.0",
"pncc_authenticated_direct_broker_integration_implemented": true,
"pncc_human_mount_registration_implemented": true,
"pncc_human_mount_registration_gate": "SATISFIED_NATIVE_FILE_PICKER_EXACT_CONFIRMATION",

View file

@ -0,0 +1,39 @@
import assert from 'node:assert/strict'
import { readFileSync } from 'node:fs'
import test from 'node:test'
const catalog = JSON.parse(readFileSync(new URL('../contracts/module-donor-admission-registry.json', import.meta.url), 'utf8'))
const numbered = JSON.parse(readFileSync(new URL('../contracts/numbered-ipc-registry.json', import.meta.url), 'utf8'))
test('chaotic donors are read-only candidates and never a bulk merge source', () => {
assert.equal(catalog.state, 'DONORS_QUARANTINED_CANDIDATES_NUMBERED_NOT_ADMITTED')
assert.equal(catalog.root_rule.repair_old_application_in_place, false)
assert.equal(catalog.root_rule.bulk_merge_or_wholesale_copy_allowed, false)
assert.equal(catalog.root_rule.one_candidate_per_admission_cycle, true)
assert.ok(catalog.donors.every((donor) => donor.state.startsWith('READ_ONLY')))
})
test('candidate coordinates are unique but are not permanent runtime module numbers', () => {
const coordinates = catalog.candidates.map((candidate) => candidate.candidate_number)
assert.equal(new Set(coordinates).size, coordinates.length)
assert.ok(catalog.candidates.every((candidate) => candidate.state.startsWith('QUARANTINED')))
assert.equal(catalog.root_rule.candidate_number_is_runtime_module_number, false)
assert.equal(catalog.root_rule.permanent_module_number_assignment_before_acceptance, false)
})
test('legacy numbering donor is rejected and the numbered IPC root remains singular', () => {
const legacy = catalog.rejected_inputs.find((candidate) => candidate.name === 'legacy_numbered_operation_runtime')
assert.equal(legacy.state, 'REJECTED_SUPERSEDED')
assert.equal(numbered.runtime.public_tauri_command, 'numbered_ipc')
assert.equal(numbered.runtime.legacy_direct_commands_allowed, false)
assert.ok(catalog.admission_gate.includes('ALLOCATE_NUMBERED_IPC_MODULE_TARGET_AND_OPERATION_COORDINATES'))
assert.ok(catalog.admission_gate.includes('IMPLEMENT_ADAPTER_WITHOUT_RAW_TAURI_INVOKE'))
})
test('hot installation is artifact, permission, self-test and rollback backed', () => {
assert.equal(catalog.hot_install_boundary.source_repository_is_directly_executable, false)
assert.equal(catalog.hot_install_boundary.immutable_signed_artifact_required, true)
assert.equal(catalog.hot_install_boundary.compatibility_manifest_required, true)
assert.equal(catalog.hot_install_boundary.rollback_on_self_test_failure, true)
assert.equal(catalog.hot_install_boundary.user_data_survives_unmount, true)
})

View file

@ -106,12 +106,30 @@ export function validateCredentialEnvironment(env) {
if (missing.length) fail(`HOLOLAKE_RELEASE_PIPELINE_CREDENTIALS_MISSING:${[...new Set(missing)].sort().join(',')}`)
}
export function materializeUpdaterPrivateKey(env, readText = (file) => fs.readFileSync(file, 'utf8')) {
if (typeof env.TAURI_SIGNING_PRIVATE_KEY === 'string' && env.TAURI_SIGNING_PRIVATE_KEY.trim()) {
return { ...env }
}
const privateKeyPath = requireText(
env.TAURI_SIGNING_PRIVATE_KEY_PATH,
'HOLOLAKE_RELEASE_PIPELINE_UPDATER_PRIVATE_KEY_PATH_REQUIRED',
)
const privateKey = requireText(
readText(privateKeyPath),
'HOLOLAKE_RELEASE_PIPELINE_UPDATER_PRIVATE_KEY_EMPTY',
)
return {
...env,
TAURI_SIGNING_PRIVATE_KEY: privateKey,
}
}
function run(program, args, options = {}) {
return execFileSync(program, args, {
cwd: root,
encoding: 'utf8',
stdio: options.capture ? ['ignore', 'pipe', 'pipe'] : 'inherit',
env: process.env,
env: options.env || process.env,
})
}
@ -194,6 +212,7 @@ export async function main(argv = process.argv.slice(2)) {
const trustFacts = validateReleaseTrust(trust)
const input = validateReleaseInput(readJson(inputPath), trustFacts)
validateCredentialEnvironment(process.env)
const credentialEnvironment = materializeUpdaterPrivateKey(process.env)
requireCleanImmutableSource(input)
if (process.platform !== 'darwin' || process.arch !== 'arm64') fail('HOLOLAKE_RELEASE_PIPELINE_BUILD_HOST_MISMATCH')
if (!process.env.APPLE_SIGNING_IDENTITY.includes(input.appleTeamIdentifier)) fail('HOLOLAKE_RELEASE_PIPELINE_SIGNING_TEAM_MISMATCH')
@ -205,7 +224,9 @@ export async function main(argv = process.argv.slice(2)) {
const buildConfig = path.join(fs.mkdtempSync(path.join(os.tmpdir(), 'hololake-release-config-')), 'tauri.release.json')
fs.writeFileSync(buildConfig, `${JSON.stringify({ bundle: { createUpdaterArtifacts: true } })}\n`, { mode: 0o600 })
try {
run('npm', ['run', 'tauri', '--', 'build', '--ci', '--config', buildConfig])
run('npm', ['run', 'tauri', '--', 'build', '--ci', '--config', buildConfig], {
env: credentialEnvironment,
})
} finally {
fs.rmSync(path.dirname(buildConfig), { recursive: true, force: true })
}
@ -217,6 +238,18 @@ export async function main(argv = process.argv.slice(2)) {
const updater = findOne(bundleRoot, (file) => file.endsWith('.app.tar.gz'), 'HOLOLAKE_RELEASE_PIPELINE_UPDATER_NOT_UNIQUE')
const updaterSignature = `${updater}.sig`
if (!fs.statSync(updaterSignature, { throwIfNoEntry: false })?.isFile()) fail('HOLOLAKE_RELEASE_PIPELINE_UPDATER_SIGNATURE_MISSING')
run('cargo', [
'run',
'--quiet',
'--manifest-path',
'src-tauri/Cargo.toml',
'--example',
'verify_updater_signature',
'--',
'src-tauri/release-trust.json',
updater,
updaterSignature,
])
if (decodeURIComponent(input.packageUrl.pathname.split('/').pop()) !== path.basename(updater)) {
fail('HOLOLAKE_RELEASE_PIPELINE_PACKAGE_URL_FILENAME_MISMATCH')
}

View file

@ -3,6 +3,7 @@ import { readFileSync } from 'node:fs'
import test from 'node:test'
import {
materializeUpdaterPrivateKey,
validateCredentialEnvironment,
validateReleaseInput,
validateReleaseTrust,
@ -76,6 +77,25 @@ test('release pipeline requires updater signing, Developer ID and Apple notariza
}))
})
test('release pipeline materializes a protected updater key path only inside the build environment', () => {
const source = {
TAURI_SIGNING_PRIVATE_KEY_PATH: '/protected/hololake-updater.key',
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: 'provided-at-runtime',
}
const hydrated = materializeUpdaterPrivateKey(source, (file) => {
assert.equal(file, source.TAURI_SIGNING_PRIVATE_KEY_PATH)
return 'encrypted-private-key-material'
})
assert.equal(hydrated.TAURI_SIGNING_PRIVATE_KEY, 'encrypted-private-key-material')
assert.equal(source.TAURI_SIGNING_PRIVATE_KEY, undefined)
})
test('release pipeline verifies the updater signature against embedded product trust before broadcast', () => {
const source = readFileSync(new URL('./release-pipeline.mjs', import.meta.url), 'utf8')
assert.match(source, /--example',\s*'verify_updater_signature'/)
assert.match(source, /src-tauri\/release-trust\.json/)
})
test('Windows updater keeps signed installation but never claims the macOS rollback boundary', () => {
const source = readFileSync(new URL('../src-tauri/src/release_update.rs', import.meta.url), 'utf8')
assert.match(source, /SIGNED_PACKAGE_VERIFIED_INSTALLING_NO_LOCAL_ROLLBACK/)

View file

@ -1499,6 +1499,7 @@ dependencies = [
"fs2",
"futures-util",
"interprocess",
"minisign-verify",
"reqwest",
"ring",
"rusqlite",

View file

@ -38,4 +38,5 @@ futures-util = "0.3"
widestring = "1"
[dev-dependencies]
minisign-verify = "0.2.5"
tempfile = "3"

View file

@ -0,0 +1,48 @@
use base64::{engine::general_purpose::STANDARD, Engine as _};
use minisign_verify::{PublicKey, Signature};
use serde_json::Value;
use std::{env, fs, path::Path};
fn decode_outer_base64(value: &str, label: &str) -> Result<String, String> {
let bytes = STANDARD
.decode(value.trim())
.map_err(|error| format!("{label}_BASE64_INVALID: {error}"))?;
String::from_utf8(bytes).map_err(|error| format!("{label}_UTF8_INVALID: {error}"))
}
fn main() -> Result<(), String> {
let arguments: Vec<String> = env::args().collect();
let [_, trust_path, updater_path, signature_path] = arguments.as_slice() else {
return Err(
"USAGE: verify_updater_signature <release-trust.json> <updater> <signature>"
.to_string(),
);
};
let trust: Value = serde_json::from_str(
&fs::read_to_string(trust_path)
.map_err(|error| format!("RELEASE_TRUST_READ_FAILED: {error}"))?,
)
.map_err(|error| format!("RELEASE_TRUST_JSON_INVALID: {error}"))?;
let encoded_public_key = trust
.get("publicKey")
.and_then(Value::as_str)
.ok_or_else(|| "RELEASE_TRUST_PUBLIC_KEY_MISSING".to_string())?;
let public_key = PublicKey::decode(&decode_outer_base64(
encoded_public_key,
"RELEASE_TRUST_PUBLIC_KEY",
)?)
.map_err(|error| format!("RELEASE_TRUST_PUBLIC_KEY_INVALID: {error}"))?;
let signature = Signature::decode(&decode_outer_base64(
&fs::read_to_string(signature_path)
.map_err(|error| format!("UPDATER_SIGNATURE_READ_FAILED: {error}"))?,
"UPDATER_SIGNATURE",
)?)
.map_err(|error| format!("UPDATER_SIGNATURE_INVALID: {error}"))?;
let updater = fs::read(Path::new(updater_path))
.map_err(|error| format!("UPDATER_ARTIFACT_READ_FAILED: {error}"))?;
public_key
.verify(&updater, &signature, true)
.map_err(|error| format!("UPDATER_SIGNATURE_VERIFICATION_FAILED: {error}"))?;
println!("HOLOLAKE_UPDATER_SIGNATURE_VERIFIED");
Ok(())
}