server/lib/src/sigstore_trust.rs
//! **The estate's Sigstore trust root** (lane HOL-OCI-EVIDENCE, coordinator 2026-10-31).
//!
//! Holger's provenance scanner checks keyless Sigstore (a Fulcio chain to a pinned root, the
//! SCT, the Rekor SET) only against a root it was given: a signed `holger.sigstore-trust/v1`
//! bundle at the floor's `rules.trust_root`, verified against `promotion.intake.trust_policy`.
//! Until now nothing staged one with the release, so holger.rs checked no keyless signature.
//! This module is the root's whole life:
//!
//! 1. **Pinned**: Sigstore's public-good `trusted_root.json` (the Fulcio CAs, the Rekor and CT
//! log keys) is the TUF target [`PINNED_SHA256`], fetched at release build from
//! [`pinned_url`] and held to that digest ([`check_pinned`]). Moving the pin is a commit.
//! 2. **Signed** ([`make`], `holger sigstore-trust make`, `cargo xtask sigstore-trust stage`):
//! the release build signs it into a trust bundle with Holger's release trust identities (a
//! signer directory kept by the release runner, three ed25519 identities) and writes the
//! bundle trust policy that pins exactly them, for the trust root, the scanner signatures and
//! the time tokens ([`ESTATE_PRODUCTS`]). Both ride the medium beside the sandbox.
//! 3. **Seeded** at boot ([`seed`]): the policy (`<data_root>/holger-trust/bundle-trust.ron`, the
//! estate RON's `trust_policy`) and the bundle (`…/sigstore-trust.holger.znippy`, its floor's
//! `trust_root`) are written before the intake chain opens, and the bundle's sequence becomes
//! the anti-rollback floor, so a refresh needs no first-import grant. A root already installed
//! at the same or a later sequence (an imported refresh) is kept.
//! 4. **Refreshed** only by a signed bundle import (the import doors, the inbox, `holger bundle
//! import`; [`crate::import_door`]): verified against the pinned policy, audited first, the
//! floor moved, installed ([`install`]), and the provenance scanner re-reads it at once.
//!
//! Nothing here dials TUF on the box: the TUF chain stops at the release build; the box trusts
//! the bundle's signature.
use std::path::{Path, PathBuf};
use serde::{Deserialize, Serialize};
use update_bundle::core::BundleCore;
use update_bundle::schema::CORE_JSON;
use update_bundle::verify::{verify_member_map_with_time, TargetSpec};
use update_bundle::TrustPolicy;
/// The product the root travels as.
pub const TRUST_PRODUCT: &str = holger_provenance::TRUST_PRODUCT;
/// Where the box keeps it, under the data root.
pub const DIR: &str = "holger-trust";
/// The signed trust bundle (the floor's `rules.trust_root`).
pub const BUNDLE_FILE: &str = "sigstore-trust.holger.znippy";
/// The bundle trust policy (the intake's `trust_policy`).
pub const POLICY_FILE: &str = "bundle-trust.ron";
/// What is installed, and from where.
pub const INSTALLED_FILE: &str = "installed.json";
/// The supplier the release build signs as.
pub const SUPPLIER: &str = "holger";
/// The products the estate's bundle trust policy lets Holger's release identities sign.
pub const ESTATE_PRODUCTS: [&str; 3] = [
TRUST_PRODUCT,
crate::import_door::SCANNER_SIGS_PRODUCT,
crate::time_floor::TIME_TOKEN_PRODUCT,
];
/// **The pinned public-good root**: the SHA-256 of Sigstore's `trusted_root.json` TUF target
/// (`provenance/tests/fixtures/sigstore/trusted_root.json`, fetched 2026-10-23).
pub const PINNED_SHA256: &str = "6494e21ea73fa7ee769f85f57d5a3e6a08725eae1e38c755fc3517c9e6bc0b66";
/// Sigstore's TUF repository's targets (consistent snapshots: a target is named by its digest).
pub const TUF_TARGETS: &str = "https://tuf-repo-cdn.sigstore.dev/targets";
/// The URL the release build fetches the pinned root from.
pub fn pinned_url() -> String {
format!("{TUF_TARGETS}/{PINNED_SHA256}.trusted_root.json")
}
/// Is `json` the pinned root, and a trust root at all? A refusal names the pin (`holger-trust-1`).
pub fn check_pinned(json: &[u8]) -> Result<(), String> {
let got = crate::attest::ArtifactDigest::of_sha256(json)
.hex()
.to_string();
if got != PINNED_SHA256 {
return Err(holger_errcode::TRUST_ROOT_NOT_PINNED.tag(format!(
"the fetched trusted_root.json hashes to sha256:{got}, and the pinned root is sha256:{PINNED_SHA256}; moving the pin is a commit"
)));
}
holger_provenance::TrustRoot::from_trusted_root_json(json)
.map_err(|e| holger_errcode::TRUST_ROOT_NOT_PINNED.tag(e.tagged()))?;
Ok(())
}
pub fn dir(data_root: &Path) -> PathBuf {
data_root.join(DIR)
}
pub fn bundle_path(data_root: &Path) -> PathBuf {
dir(data_root).join(BUNDLE_FILE)
}
pub fn policy_path(data_root: &Path) -> PathBuf {
dir(data_root).join(POLICY_FILE)
}
pub fn installed_path(data_root: &Path) -> PathBuf {
dir(data_root).join(INSTALLED_FILE)
}
/// What is installed.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Installed {
pub bundle_id: String,
pub sequence: u64,
/// The SHA-256 of the `trusted_root.json` it carries.
pub trusted_root_sha256: String,
/// `medium` (seeded at boot) or `import` (a refresh).
pub from: String,
pub at: String,
/// Where the bundle is.
pub path: String,
}
/// What is installed under `data_root`, when anything is.
pub fn installed(data_root: &Path) -> Option<Installed> {
serde_json::from_slice(&std::fs::read(installed_path(data_root)).ok()?).ok()
}
fn write_installed(data_root: &Path, i: &Installed) -> Result<(), String> {
let bytes = serde_json::to_vec_pretty(i).map_err(|e| e.to_string())?;
update_bundle::container::atomic_write(&installed_path(data_root), &bytes)
.map_err(|e| format!("{}: {e}", installed_path(data_root).display()))
}
/// A verified trust bundle at `path`: its core, its candidate digest, the root's digest.
fn verified(path: &Path, policy: &TrustPolicy) -> Result<(BundleCore, String, String), String> {
let members = update_bundle::container::load_bundle_verified(path, policy)
.map_err(|e| format!("{}: {e}", path.display()))?;
let core = BundleCore::from_json_bytes(
members
.get(CORE_JSON)
.ok_or("the bundle has no core.json")?,
)
.map_err(|e| format!("core.json: {e}"))?;
let target = TargetSpec {
product: Some(TRUST_PRODUCT.to_string()),
model: None,
revision: None,
};
let report = verify_member_map_with_time(
&members,
policy,
&target,
None,
&crate::import_door::EstateTime,
)
.map_err(|e| format!("{}: {}", path.display(), e.code().tag(&e)))?;
let v = holger_provenance::TrustRoot::verified_bundle(
path,
policy,
&crate::import_door::EstateTime,
)
.map_err(|e| e.tagged())?;
holger_provenance::TrustRoot::from_trusted_root_json(&v.trusted_root_json)
.map_err(|e| e.tagged())?;
Ok((
core,
report.candidate_digest,
crate::attest::ArtifactDigest::of_sha256(&v.trusted_root_json)
.hex()
.to_string(),
))
}
/// What a seed did.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Seeded {
pub sequence: u64,
/// The installed root was the same or later: nothing written but the policy.
pub kept: bool,
}
/// **Seed the root from the medium** (the box's boot, before the intake chain opens):
/// `fetch` reads a medium entry by name ([`BUNDLE_FILE`], [`POLICY_FILE`]). `Ok(None)`: the
/// medium carries no root. The policy is the medium's (it is the root of the bundle trust);
/// the bundle is written unless one of the same or a later sequence that still verifies is
/// installed; the floor is raised to the medium's sequence. A refusal is `holger-trust-2`.
pub fn seed(
data_root: &Path,
fetch: &dyn Fn(&str) -> Option<Vec<u8>>,
) -> Result<Option<Seeded>, String> {
let refuse = |w: String| holger_errcode::TRUST_ROOT_SEED_REFUSED.tag(w);
let (Some(bundle), Some(policy_bytes)) = (fetch(BUNDLE_FILE), fetch(POLICY_FILE)) else {
return Ok(None);
};
let policy = TrustPolicy::from_ron(&String::from_utf8_lossy(&policy_bytes))
.map_err(|e| refuse(format!("the medium's {POLICY_FILE}: {e}")))?;
let d = dir(data_root);
std::fs::create_dir_all(&d).map_err(|e| refuse(format!("{}: {e}", d.display())))?;
let staged = d.join(".medium.tmp");
std::fs::write(&staged, &bundle).map_err(|e| refuse(format!("{}: {e}", staged.display())))?;
let medium = verified(&staged, &policy);
let (core, digest, root_sha) = match medium {
Ok(v) => v,
Err(e) => {
let _ = std::fs::remove_file(&staged);
return Err(refuse(format!(
"the medium's trust bundle does not verify against the medium's policy: {e}"
)));
}
};
update_bundle::container::atomic_write(&policy_path(data_root), &policy_bytes)
.map_err(|e| refuse(format!("{POLICY_FILE}: {e}")))?;
let keep = installed(data_root)
.filter(|i| i.sequence >= core.sequence)
.is_some()
&& verified(&bundle_path(data_root), &policy).is_ok();
if keep {
let _ = std::fs::remove_file(&staged);
} else {
std::fs::rename(&staged, bundle_path(data_root))
.map_err(|e| refuse(format!("{BUNDLE_FILE}: {e}")))?;
write_installed(
data_root,
&Installed {
bundle_id: core.bundle_id.clone(),
sequence: core.sequence,
trusted_root_sha256: root_sha,
from: "medium".into(),
at: chrono::Utc::now().to_rfc3339(),
path: bundle_path(data_root).display().to_string(),
},
)
.map_err(refuse)?;
}
raise_floor(data_root, &core, &digest).map_err(refuse)?;
Ok(Some(Seeded {
sequence: core.sequence,
kept: keep,
}))
}
/// The anti-rollback floor of the trust product, at least `core`'s sequence.
fn raise_floor(data_root: &Path, core: &BundleCore, digest: &str) -> Result<(), String> {
use update_bundle::rollback::{AcceptedEntry, RollbackStore};
let store = RollbackStore::open(data_root.join(crate::import_door::UPDATE_STATE_DIR));
let floor = store
.floor(&core.supplier, &core.product)
.map_err(|e| format!("the floor: {e}"))?;
if floor.accepted().is_some_and(|s| {
s.highest_accepted_sequence()
.is_some_and(|h| h >= core.sequence)
}) {
return Ok(());
}
store
.record(
&core.supplier,
&core.product,
&AcceptedEntry {
sequence: core.sequence,
bundle_id: core.bundle_id.clone(),
core_digest: digest.to_string(),
accepted_at: chrono::Utc::now(),
},
)
.map_err(|e| format!("the floor: {e}"))
}
/// **Install an imported refresh** (the import door, after the audit and the floor): the
/// verified `members` packed at `target` (the floor's `trust_root`, else [`bundle_path`]).
pub fn install(
data_root: &Path,
target: &Path,
members: &update_bundle::MemberMap,
core: &BundleCore,
) -> Result<Installed, String> {
let fail = |w: String| holger_errcode::TRUST_ROOT_SEED_REFUSED.tag(w);
if let Some(p) = target.parent() {
std::fs::create_dir_all(p).map_err(|e| fail(format!("{}: {e}", p.display())))?;
}
let staged = target.with_extension("import.tmp");
update_bundle::pack_bundle(members, &staged)
.map_err(|e| fail(format!("pack {}: {e}", staged.display())))?;
std::fs::rename(&staged, target).map_err(|e| fail(format!("{}: {e}", target.display())))?;
let root = members
.get(&format!(
"{}{}",
update_bundle::schema::PAYLOAD_PREFIX,
holger_provenance::trust::TRUSTED_ROOT_REL
))
.cloned()
.unwrap_or_default();
let i = Installed {
bundle_id: core.bundle_id.clone(),
sequence: core.sequence,
trusted_root_sha256: crate::attest::ArtifactDigest::of_sha256(&root)
.hex()
.to_string(),
from: "import".into(),
at: chrono::Utc::now().to_rfc3339(),
path: target.display().to_string(),
};
write_installed(data_root, &i).map_err(fail)?;
Ok(i)
}
/// **Sign a trust root** (the release build, `holger sigstore-trust make`): `root` (Sigstore's
/// `trusted_root.json`) into a `holger.sigstore-trust/v1` bundle at `out`, sequence `sequence`,
/// valid `valid_days`, signed by the three identities under `signer_dir` (minted on first use,
/// keys 0600; the release runner keeps them). Returns the bundle trust policy that pins them,
/// for [`ESTATE_PRODUCTS`].
#[cfg(feature = "export-push")]
pub fn make(
root: &[u8],
signer_dir: &Path,
out: &Path,
sequence: u64,
valid_days: i64,
source_uri: Option<String>,
) -> anyhow::Result<TrustPolicy> {
anyhow::ensure!(valid_days > 0, "a trust bundle is valid for at least a day");
let signer = crate::export_push::Signer::open_or_mint(signer_dir)?;
let mut policy = signer.policy(TRUST_PRODUCT);
policy.allowed_products = ESTATE_PRODUCTS.iter().map(|p| p.to_string()).collect();
let (approvals, release) = signer.bundle_signers();
let now = chrono::Utc::now();
holger_provenance::build_trust_bundle(
root,
holger_provenance::TrustBundleOptions {
sequence,
created_at: now - chrono::Duration::minutes(1),
expires_at: Some(now + chrono::Duration::days(valid_days)),
supplier: SUPPLIER.into(),
producer: format!("holger {} sigstore-trust make", env!("CARGO_PKG_VERSION")),
source_uri,
policy: policy.clone(),
approvals,
release,
out: out.to_path_buf(),
apk_keys: Vec::new(),
pgp_keys: Vec::new(),
},
)?;
Ok(policy)
}