Skip to main content

clawft_kernel/workload_pkg/
store.rs

1//! Persist verified packages in [`ArtifactStore`] and re-verify from it.
2//!
3//! Every file (and the manifest itself) is stored under its BLAKE3 hash.
4//! Nothing is stored before the package verifies.
5
6use std::path::Path;
7
8use crate::artifact_store::{ArtifactStore, ArtifactType};
9use crate::workload_kind::KindRegistry;
10
11use super::manifest::{FileRef, MANIFEST_FILE, MAX_MANIFEST_BYTES};
12use super::trust::TrustAnchors;
13use super::verify::{
14    DirSource, FileSource, VerifiedPackage, VerifyError, VerifyPolicy, read_bounded,
15    verify_with_source, verify_with_source_in,
16};
17
18/// Result of [`store_package`].
19#[derive(Debug, Clone, PartialEq, Eq)]
20pub struct StoredPackage {
21    /// BLAKE3 of the signed statement.
22    pub package_id: String,
23    /// `ArtifactStore` hash of the `cogpkg.json` bytes; pass it to
24    /// [`verify_stored`].
25    pub manifest_hash: String,
26    /// `(package path, artifact hash)` for every file.
27    pub files: Vec<(String, String)>,
28}
29
30/// Reads package files from an [`ArtifactStore`] by hash.
31pub struct StoreSource<'a> {
32    store: &'a ArtifactStore,
33}
34
35impl<'a> StoreSource<'a> {
36    /// Source over `store`.
37    pub fn new(store: &'a ArtifactStore) -> Self {
38        Self { store }
39    }
40}
41
42impl FileSource for StoreSource<'_> {
43    fn read(&self, file: &FileRef) -> Result<Vec<u8>, VerifyError> {
44        if !self.store.contains(&file.blake3) {
45            return Err(VerifyError::FileMissing {
46                path: file.path.clone(),
47            });
48        }
49        self.store.load(&file.blake3).map_err(|e| {
50            let msg = e.to_string();
51            if msg.contains("integrity error") {
52                // The store re-hashes on load; a mismatch means the stored
53                // bytes were altered.
54                VerifyError::HashMismatch {
55                    path: file.path.clone(),
56                    expected: file.blake3.clone(),
57                    actual: "stored content no longer matches its hash".into(),
58                }
59            } else {
60                VerifyError::Io {
61                    path: file.path.clone(),
62                    msg,
63                }
64            }
65        })
66    }
67}
68
69fn store_err(path: &str, e: impl std::fmt::Display) -> VerifyError {
70    VerifyError::Io {
71        path: path.to_string(),
72        msg: e.to_string(),
73    }
74}
75
76/// Verify the package in `dir`, then store its manifest and files.
77pub fn store_package(
78    store: &ArtifactStore,
79    dir: &Path,
80    anchors: &TrustAnchors,
81    policy: &VerifyPolicy,
82) -> Result<StoredPackage, VerifyError> {
83    let manifest = read_bounded(
84        &dir.join(MANIFEST_FILE),
85        MANIFEST_FILE,
86        MAX_MANIFEST_BYTES as u64,
87    )?;
88    let source = DirSource::new(dir);
89    let verified = verify_with_source(&manifest, &source, anchors, policy)?;
90
91    let mut files = Vec::new();
92    for file in verified.body.files() {
93        // Re-read and re-check: the directory could change after verify.
94        let content = source.read(file)?;
95        super::verify::check_file(file, &content)?;
96        let kind = if file.path == "cog.toml" {
97            ArtifactType::ConfigBundle
98        } else {
99            ArtifactType::Generic
100        };
101        let hash = store
102            .store(&content, kind)
103            .map_err(|e| store_err(&file.path, e))?;
104        files.push((file.path.clone(), hash));
105    }
106    let manifest_hash = store
107        .store(&manifest, ArtifactType::AppManifest)
108        .map_err(|e| store_err(MANIFEST_FILE, e))?;
109    Ok(StoredPackage {
110        package_id: verified.package_id,
111        manifest_hash,
112        files,
113    })
114}
115
116/// Load a manifest from `store` by its artifact hash and verify it, reading
117/// every file from the store.
118pub fn verify_stored(
119    store: &ArtifactStore,
120    manifest_hash: &str,
121    anchors: &TrustAnchors,
122    policy: &VerifyPolicy,
123) -> Result<VerifiedPackage, VerifyError> {
124    verify_stored_in(
125        store,
126        manifest_hash,
127        anchors,
128        policy,
129        &KindRegistry::builtin(),
130    )
131}
132
133/// [`verify_stored`] against a caller-supplied kind registry.
134pub fn verify_stored_in(
135    store: &ArtifactStore,
136    manifest_hash: &str,
137    anchors: &TrustAnchors,
138    policy: &VerifyPolicy,
139    kinds: &KindRegistry,
140) -> Result<VerifiedPackage, VerifyError> {
141    if !store.contains(manifest_hash) {
142        return Err(VerifyError::FileMissing {
143            path: format!("{MANIFEST_FILE} ({manifest_hash})"),
144        });
145    }
146    let manifest = store
147        .load(manifest_hash)
148        .map_err(|e| store_err(MANIFEST_FILE, e))?;
149    verify_with_source_in(&manifest, &StoreSource::new(store), anchors, policy, kinds)
150}