Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/holger

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)
}