use std::path::Path;
use crate::artifact_store::{ArtifactStore, ArtifactType};
use crate::workload_kind::KindRegistry;
use super::manifest::{FileRef, MANIFEST_FILE, MAX_MANIFEST_BYTES};
use super::trust::TrustAnchors;
use super::verify::{
DirSource, FileSource, VerifiedPackage, VerifyError, VerifyPolicy, read_bounded,
verify_with_source, verify_with_source_in,
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StoredPackage {
pub package_id: String,
pub manifest_hash: String,
pub files: Vec<(String, String)>,
}
pub struct StoreSource<'a> {
store: &'a ArtifactStore,
}
impl<'a> StoreSource<'a> {
pub fn new(store: &'a ArtifactStore) -> Self {
Self { store }
}
}
impl FileSource for StoreSource<'_> {
fn read(&self, file: &FileRef) -> Result<Vec<u8>, VerifyError> {
if !self.store.contains(&file.blake3) {
return Err(VerifyError::FileMissing {
path: file.path.clone(),
});
}
self.store.load(&file.blake3).map_err(|e| {
let msg = e.to_string();
if msg.contains("integrity error") {
VerifyError::HashMismatch {
path: file.path.clone(),
expected: file.blake3.clone(),
actual: "stored content no longer matches its hash".into(),
}
} else {
VerifyError::Io {
path: file.path.clone(),
msg,
}
}
})
}
}
fn store_err(path: &str, e: impl std::fmt::Display) -> VerifyError {
VerifyError::Io {
path: path.to_string(),
msg: e.to_string(),
}
}
pub fn store_package(
store: &ArtifactStore,
dir: &Path,
anchors: &TrustAnchors,
policy: &VerifyPolicy,
) -> Result<StoredPackage, VerifyError> {
let manifest = read_bounded(
&dir.join(MANIFEST_FILE),
MANIFEST_FILE,
MAX_MANIFEST_BYTES as u64,
)?;
let source = DirSource::new(dir);
let verified = verify_with_source(&manifest, &source, anchors, policy)?;
let mut files = Vec::new();
for file in verified.body.files() {
let content = source.read(file)?;
super::verify::check_file(file, &content)?;
let kind = if file.path == "cog.toml" {
ArtifactType::ConfigBundle
} else {
ArtifactType::Generic
};
let hash = store
.store(&content, kind)
.map_err(|e| store_err(&file.path, e))?;
files.push((file.path.clone(), hash));
}
let manifest_hash = store
.store(&manifest, ArtifactType::AppManifest)
.map_err(|e| store_err(MANIFEST_FILE, e))?;
Ok(StoredPackage {
package_id: verified.package_id,
manifest_hash,
files,
})
}
pub fn verify_stored(
store: &ArtifactStore,
manifest_hash: &str,
anchors: &TrustAnchors,
policy: &VerifyPolicy,
) -> Result<VerifiedPackage, VerifyError> {
verify_stored_in(
store,
manifest_hash,
anchors,
policy,
&KindRegistry::builtin(),
)
}
pub fn verify_stored_in(
store: &ArtifactStore,
manifest_hash: &str,
anchors: &TrustAnchors,
policy: &VerifyPolicy,
kinds: &KindRegistry,
) -> Result<VerifiedPackage, VerifyError> {
if !store.contains(manifest_hash) {
return Err(VerifyError::FileMissing {
path: format!("{MANIFEST_FILE} ({manifest_hash})"),
});
}
let manifest = store
.load(manifest_hash)
.map_err(|e| store_err(MANIFEST_FILE, e))?;
verify_with_source_in(&manifest, &StoreSource::new(store), anchors, policy, kinds)
}