dotlock-bin 0.1.6

Encrypted project-local environment variables manager
use std::fs;

use crate::{
    audit::log::{audit_log_path, compute_entry_hash, read_all_entries},
    crypto::share::verify_audit_entry_hash_signature,
    domain::{error::DotLockError, model::DotLockResult},
    storage::identity::{load_local_identity_metadata, public_key_path},
};

const ZERO_HASH: &str = "sha256:0000000000000000000000000000000000000000000000000000000000000000";

pub fn verify_log(strict: bool) -> DotLockResult<()> {
    let path = audit_log_path()?;
    let entries = read_all_entries(&path)?;
    let local_identity = load_local_identity_metadata().ok();
    let local_public_key = fs::read_to_string(public_key_path()).ok();
    let mut prev_hash = ZERO_HASH.to_string();
    let mut anonymous = 0usize;

    for (index, entry) in entries.iter().enumerate() {
        let line = index + 1;
        if entry.prev_hash != prev_hash {
            return Err(DotLockError::Crypto(format!(
                "audit verify failed at line {line}: prev_hash mismatch"
            )));
        }

        let expected =
            compute_entry_hash(entry.ts, &entry.action, &entry.payload, &entry.prev_hash)?;
        if entry.entry_hash != expected {
            return Err(DotLockError::Crypto(format!(
                "audit verify failed at line {line}: hash mismatch"
            )));
        }

        if entry.signer_fingerprint == "anonymous" || entry.signature.is_empty() {
            anonymous += 1;
            if strict {
                return Err(DotLockError::Crypto(format!(
                    "audit verify failed at line {line}: anonymous entry rejected by --strict"
                )));
            }
        } else if let (Some(identity), Some(public_key)) = (&local_identity, &local_public_key) {
            if identity.fingerprint != entry.signer_fingerprint {
                return Err(DotLockError::Crypto(format!(
                    "audit verify failed at line {line}: signer fingerprint is not the local identity"
                )));
            }
            verify_audit_entry_hash_signature(&entry.entry_hash, &entry.signature, public_key)
                .map_err(|_| {
                    DotLockError::Crypto(format!(
                        "audit verify failed at line {line}: signature invalid"
                    ))
                })?;
        } else {
            return Err(DotLockError::Crypto(format!(
                "audit verify failed at line {line}: signer identity is unavailable"
            )));
        }

        prev_hash = entry.entry_hash.clone();
    }

    if anonymous > 0 {
        eprintln!("warn: {anonymous} anonymous audit entrie(s) had no signature");
    }
    println!("ok: audit log verified ({} entrie(s))", entries.len());
    Ok(())
}