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

nordisk/dwarves

crates/dwarves-slsa/src/subjects.rs

//! The subjects: the sha256 of every output the build left in its out directory.

use std::io::Read;
use std::path::Path;

use sha2::{Digest, Sha256};

use crate::statement::Subject;
use crate::{bail, Result};

/// Lowercase hex sha256.
pub fn sha256_hex(bytes: &[u8]) -> String {
    hex::encode(Sha256::digest(bytes))
}

/// The sha256 of a file, streamed.
pub fn sha256_file(path: &Path) -> Result<String> {
    let mut f =
        std::fs::File::open(path).map_err(|e| crate::Error(format!("{}: {e}", path.display())))?;
    let mut h = Sha256::new();
    let mut buf = vec![0u8; 1 << 16];
    loop {
        let n = f.read(&mut buf)?;
        if n == 0 {
            break;
        }
        h.update(&buf[..n]);
    }
    Ok(hex::encode(h.finalize()))
}

/// Every regular file below `dir`, named by its path relative to `dir` (`/`
/// separated), sorted by name. A symlink or any other kind of file is refused: the
/// build cannot point a subject at something outside its out directory.
pub fn subjects_of_dir(dir: &Path) -> Result<Vec<Subject>> {
    if !std::fs::symlink_metadata(dir)
        .map(|m| m.is_dir())
        .unwrap_or(false)
    {
        bail!(
            "out directory {} is missing (or not a directory)",
            dir.display()
        );
    }
    let mut out = Vec::new();
    walk(dir, "", &mut out)?;
    out.sort_by(|a, b| a.name.cmp(&b.name));
    Ok(out)
}

fn walk(dir: &Path, prefix: &str, out: &mut Vec<Subject>) -> Result<()> {
    for e in std::fs::read_dir(dir)? {
        let e = e?;
        let name = e
            .file_name()
            .into_string()
            .map_err(|n| crate::Error(format!("a file name that is not UTF-8: {n:?}")))?;
        let rel = if prefix.is_empty() {
            name
        } else {
            format!("{prefix}/{name}")
        };
        let t = e.file_type()?;
        if t.is_symlink() {
            bail!("{rel} in the out directory is a symlink: refused");
        } else if t.is_dir() {
            walk(&e.path(), &rel, out)?;
        } else if t.is_file() {
            out.push(Subject::sha256(rel, sha256_file(&e.path())?));
        } else {
            bail!("{rel} in the out directory is not a regular file: refused");
        }
    }
    Ok(())
}