#[cfg(test)]
mod regressions;
mod secure;
#[cfg(test)]
mod tests;
use crate::model::artifacts::ArtifactChecksumRecord;
use crate::model::evidence_digest::EvidenceDigest;
#[cfg(any(unix, test))]
use std::io::Read;
use std::{
io,
path::{Component, Path, PathBuf},
};
use thiserror::Error;
pub fn checksum_directory(path: &Path) -> Result<ArtifactChecksumRecord, ArtifactError> {
secure::checksum_directory(path)
}
#[cfg(any(unix, test))]
pub(crate) fn checksum_reader(
reader: &mut impl Read,
) -> Result<ArtifactChecksumRecord, ArtifactError> {
let identity =
ic_host_artifacts::artifact::hash_reader(reader, u64::MAX).map_err(io::Error::from)?;
Ok(ArtifactChecksumRecord::from_digest(
*identity.sha256.as_bytes(),
))
}
pub fn checksum_relative_files(
mut files: Vec<(PathBuf, ArtifactChecksumRecord)>,
) -> Result<ArtifactChecksumRecord, DirectoryChecksumError> {
files.sort_by(|left, right| left.0.cmp(&right.0));
let mut hasher = EvidenceDigest::new(&[]);
let mut previous = None;
for (relative, checksum) in &files {
let name = relative
.to_str()
.ok_or_else(|| DirectoryChecksumError::NonUtf8Path {
path: relative.clone(),
})?;
if name.contains('\0')
|| name.split('/').any(|part| matches!(part, "" | "." | ".."))
|| !relative
.components()
.all(|part| matches!(part, Component::Normal(_)))
{
return Err(DirectoryChecksumError::InvalidRelativePath {
path: relative.clone(),
});
}
if previous == Some(relative) {
return Err(DirectoryChecksumError::DuplicatePath {
path: relative.clone(),
});
}
previous = Some(relative);
hasher.append(name.as_bytes());
hasher.byte(0);
hasher.append(checksum.hash().as_bytes());
hasher.append(b"\n");
}
Ok(hasher.finish())
}
#[derive(Clone, Debug, Eq, Error, PartialEq)]
pub enum DirectoryChecksumError {
#[error("directory checksum path is not UTF-8: {path:?}")]
NonUtf8Path {
path: PathBuf,
},
#[error("directory checksum path is not canonical and relative: {path:?}")]
InvalidRelativePath {
path: PathBuf,
},
#[error("duplicate directory checksum path: {path:?}")]
DuplicatePath {
path: PathBuf,
},
}
impl From<DirectoryChecksumError> for ArtifactError {
fn from(error: DirectoryChecksumError) -> Self {
match error {
DirectoryChecksumError::NonUtf8Path { path } => Self::NonUtf8Path { path },
error => Self::Io(io::Error::new(io::ErrorKind::InvalidData, error)),
}
}
}
#[derive(Debug, Error)]
pub enum ArtifactError {
#[error("artifact path is not UTF-8: {path:?}")]
NonUtf8Path {
path: PathBuf,
},
#[error(transparent)]
Io(#[from] io::Error),
#[error("unsupported artifact entry at {path}: {kind}")]
UnsupportedEntry {
path: String,
kind: String,
},
#[error("secure artifact traversal is unsupported on platform {0}")]
UnsupportedPlatform(&'static str),
}